From f855abe3d692077e545a75c5d39615e3daaca8f5 Mon Sep 17 00:00:00 2001 From: Alex Nahas Date: Thu, 1 Oct 2026 20:45:03 -0700 Subject: [PATCH 1/3] feat: add agentInvoked and respondWith() to SubmitEvent --- README.md | 1 + index.d.ts | 20 ++++++++++++++++++++ index.test-d.ts | 22 ++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/README.md b/README.md index 8049f51..7f8aea4 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ This package defines Typescript types (`.d.ts`) for the [WebMCP specification](https://webmachinelearning.github.io/webmcp). Use this package to augment the ambient [`"dom"`](https://www.typescriptlang.org/docs/handbook/compiler-options.html#compiler-options) type definitions with the new definitions for WebMCP. +It also adds `agentInvoked` and `respondWith()` to `SubmitEvent` from the [declarative API explainer](https://github.com/webmachinelearning/webmcp/blob/main/declarative-api-explainer.md), which the draft does not specify yet. ## What are declaration files? diff --git a/index.d.ts b/index.d.ts index b743902..e21bce4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -323,6 +323,26 @@ var ToolCancelEvent: { prototype: ToolCancelEvent; new(type: string, eventInitDict?: ToolCancelEventInit): ToolCancelEvent; }; + +interface SubmitEvent { + /** + * Whether an agent caused this submission by invoking the form's declarative tool. + * Absent in browsers without declarative tools, so check it before calling `respondWith()`. + * + * Defined by the declarative API explainer; the draft does not specify declarative tools yet. + * @see https://github.com/webmachinelearning/webmcp/blob/main/declarative-api-explainer.md#events + */ + readonly agentInvoked: boolean; + /** + * Responds to the agent that invoked the form's declarative tool instead of letting the form navigate. + * Call `preventDefault()` first. + * + * Defined by the declarative API explainer; the draft does not specify declarative tools yet. + * @param agentResponse A promise that resolves to the response the agent will consume. + * @see https://github.com/webmachinelearning/webmcp/blob/main/declarative-api-explainer.md#events + */ + respondWith(agentResponse: PromiseLike): void; +} } export type { WebMCP }; diff --git a/index.test-d.ts b/index.test-d.ts index 56d0505..87be86d 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -233,3 +233,25 @@ test('supports the toolchange, toolactivated, and toolcancel events', () => { expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); }); + +test("adds the declarative explainer's SubmitEvent members", () => { + document.forms[0].addEventListener('submit', (event) => { + expectTypeOf(event.agentInvoked).toEqualTypeOf(); + expectTypeOf(event.respondWith).parameters.toEqualTypeOf<[agentResponse: PromiseLike]>(); + expectTypeOf(event.respondWith).returns.toEqualTypeOf(); + if (event.agentInvoked) { + event.preventDefault(); + event.respondWith(fetch('/search').then((response) => response.json())); + } + + // @ts-expect-error agentInvoked is read-only. + event.agentInvoked = true; + // @ts-expect-error respondWith() requires a response. + event.respondWith(); + // @ts-expect-error Pass the response promise, not a function that creates it. + event.respondWith(async () => 'done'); + }); + + // @ts-expect-error SubmitEventInit has no agentInvoked member; only Chromium adds one. + new SubmitEvent('submit', { agentInvoked: true }); +}); From 788e3fe9a140430258ac321127f18c0cd23bac79 Mon Sep 17 00:00:00 2001 From: Alex Nahas Date: Sat, 3 Oct 2026 00:32:57 -0700 Subject: [PATCH 2/3] docs(types): clarify submission timing and compiler setup State that respondWith() must run during submit dispatch after preventDefault(). Keep this contract in its JSDoc instead of repeating the event's broader purpose. Replace the invalid typeRoots and unnecessary Webpack recipes with two compiled TypeScript configuration examples. Keep the SubmitEvent type assertion readable. Validated: 14 type tests with strict null checking enabled and disabled, both README configurations, package contents, and NodeNext/Bundler consumers. --- README.md | 57 ++++++++----------------------------------------- index.d.ts | 4 +--- index.test-d.ts | 4 +++- 3 files changed, 13 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 7f8aea4..f3e0cb6 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,9 @@ -# Typescript Type Definitions for WebMCP +# TypeScript definitions for WebMCP -This package defines Typescript types (`.d.ts`) for the [WebMCP specification](https://webmachinelearning.github.io/webmcp). - -Use this package to augment the ambient [`"dom"`](https://www.typescriptlang.org/docs/handbook/compiler-options.html#compiler-options) type definitions with the new definitions for WebMCP. +This package augments TypeScript's DOM types with definitions for the [WebMCP specification](https://webmachinelearning.github.io/webmcp/). It also adds `agentInvoked` and `respondWith()` to `SubmitEvent` from the [declarative API explainer](https://github.com/webmachinelearning/webmcp/blob/main/declarative-api-explainer.md), which the draft does not specify yet. -## What are declaration files? - -See the [TypeScript handbook](http://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html). - - -## How can I use them? - -### Install +## Install - npm: `npm install --save-dev webmcp-types` - yarn: `yarn add --dev webmcp-types` @@ -20,61 +11,31 @@ See the [TypeScript handbook](http://www.typescriptlang.org/docs/handbook/declar This package requires TypeScript 5.0 or newer. -### Configure +## Configure -Since this package is outside DefinitelyTyped, the dependency won't be picked up automatically. -There are several ways to add a additional TypeScript type definition dependencies to your TypeScript project: +Add `"webmcp-types"` to [`compilerOptions.types`](https://www.typescriptlang.org/tsconfig/types.html) in your `tsconfig.json`, keeping any entries you already have: -#### TypeScript `tsc` and `tsc`-based bundlers - -In `tsconfig.json`: - -```js +```json { - // ... "compilerOptions": { - // ... "types": ["webmcp-types"] } } ``` -Or you can use `typeRoots`: - -```js -{ - // ... - "compilerOptions": { - // ... - "typeRoots": ["./node_modules/webmcp-types", "./node_modules/@types"] - } -} -``` - -#### Inline in TypeScript - -This may work better if your toolchain doesn't read `tsconfig.json`. +Alternatively, add a [type reference](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-) at the top of a `.d.ts` file included in your project: ```ts /// ``` -#### Webpack - -If you use Webpack and the options above aren't sufficient (this has not been verified), -you may need the following in `webpack.config.js`: - -```js -"types": ["webmcp-types"] -``` - -### Run the type tests +## Run the type tests - `npm install` - `npm test` The tests in `index.test-d.ts` are statically checked with [Vitest typecheck mode](https://vitest.dev/guide/testing-types) against `tsconfig.json`; they are never executed. -### Publish a new npm package version +## Publish [Release Please](https://github.com/googleapis/release-please) keeps a release pull request open with the next version and changelog, based on [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) on `main`. Merging it creates the GitHub release and publishes to npm through [trusted publishing](https://docs.npmjs.com/trusted-publishers/); no npm token or local publish step is needed. diff --git a/index.d.ts b/index.d.ts index e21bce4..5231539 100644 --- a/index.d.ts +++ b/index.d.ts @@ -329,15 +329,13 @@ interface SubmitEvent { * Whether an agent caused this submission by invoking the form's declarative tool. * Absent in browsers without declarative tools, so check it before calling `respondWith()`. * - * Defined by the declarative API explainer; the draft does not specify declarative tools yet. * @see https://github.com/webmachinelearning/webmcp/blob/main/declarative-api-explainer.md#events */ readonly agentInvoked: boolean; /** * Responds to the agent that invoked the form's declarative tool instead of letting the form navigate. - * Call `preventDefault()` first. + * Call during submit event dispatch, after calling `preventDefault()`. * - * Defined by the declarative API explainer; the draft does not specify declarative tools yet. * @param agentResponse A promise that resolves to the response the agent will consume. * @see https://github.com/webmachinelearning/webmcp/blob/main/declarative-api-explainer.md#events */ diff --git a/index.test-d.ts b/index.test-d.ts index 87be86d..b222e8b 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -237,8 +237,10 @@ test('supports the toolchange, toolactivated, and toolcancel events', () => { test("adds the declarative explainer's SubmitEvent members", () => { document.forms[0].addEventListener('submit', (event) => { expectTypeOf(event.agentInvoked).toEqualTypeOf(); - expectTypeOf(event.respondWith).parameters.toEqualTypeOf<[agentResponse: PromiseLike]>(); + expectTypeOf(event.respondWith).parameters + .toEqualTypeOf<[agentResponse: PromiseLike]>(); expectTypeOf(event.respondWith).returns.toEqualTypeOf(); + if (event.agentInvoked) { event.preventDefault(); event.respondWith(fetch('/search').then((response) => response.json())); From de49cbaac988457660dd0634be1fc394392ce0c3 Mon Sep 17 00:00:00 2001 From: Alex Nahas Date: Sat, 3 Oct 2026 00:50:32 -0700 Subject: [PATCH 3/3] chore(types): finish package cleanup and lifecycle coverage Remove the empty runtime entry and redundant module marker; the package is already a module through its public type export. Document the lifecycle event initializer defaults and cover listener receivers, optional initialization, inherited fields, narrowing, and rejected inputs. Keep these contracts covered without duplicating constructor signatures in tests. Validated: 14 type tests with strict null checking enabled and disabled, the four-file package inventory, and packed NodeNext/Bundler consumers. --- index.d.ts | 5 +++-- index.test-d.ts | 28 ++++++++++++++++++++++++---- package.json | 1 - 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/index.d.ts b/index.d.ts index 5231539..476d60c 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,11 +1,10 @@ -export {}; - type IsUnion = T extends TWhole ? [TWhole] extends [T] ? false : true : never; type NonUnionTupleElements = { [TIndex in keyof TTuple]: true extends IsUnion ? never : TTuple[TIndex]; }[number]; type Simplify = { [TKey in keyof T]: T[TKey] } & {}; +// Only literal names in a literal tuple are required; widened or union shapes are runtime choices. type JsonSchemaRequiredKeys = TSchema extends { readonly required: infer TRequired extends readonly string[]; } ? number extends TRequired["length"] @@ -283,6 +282,7 @@ interface Document { interface ToolActivatedEventInit extends EventInit { /** * The name of the tool whose execution has started. + * @default "" */ toolName?: string; } @@ -305,6 +305,7 @@ var ToolActivatedEvent: { interface ToolCancelEventInit extends EventInit { /** * The name of the tool whose execution was cancelled. + * @default "" */ toolName?: string; } diff --git a/index.test-d.ts b/index.test-d.ts index b222e8b..32dd057 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -194,7 +194,8 @@ test('supports the toolchange, toolactivated, and toolcancel events', () => { expectTypeOf(event).toEqualTypeOf(); }); - document.modelContext?.addEventListener('toolactivated', (event) => { + document.modelContext?.addEventListener('toolactivated', function (event) { + expectTypeOf(this).toEqualTypeOf(); expectTypeOf(event).toEqualTypeOf(); expectTypeOf(event.toolName).toEqualTypeOf(); }); @@ -203,12 +204,14 @@ test('supports the toolchange, toolactivated, and toolcancel events', () => { expectTypeOf(event).toEqualTypeOf(); expectTypeOf(event.toolName).toEqualTypeOf(); }); + window.addEventListener('toolactivated', (event) => expectTypeOf(event).toEqualTypeOf()); if (document.modelContext) { document.modelContext.ontoolchange = (event) => { expectTypeOf(event).toEqualTypeOf(); }; - document.modelContext.ontoolactivated = (event) => { + document.modelContext.ontoolactivated = function (event) { + expectTypeOf(this).toEqualTypeOf(); expectTypeOf(event).toEqualTypeOf(); }; document.modelContext.ontoolcancel = (event) => { @@ -217,12 +220,29 @@ test('supports the toolchange, toolactivated, and toolcancel events', () => { document.modelContext.ontoolchange = null; document.modelContext.ontoolactivated = null; document.modelContext.ontoolcancel = null; + // @ts-expect-error toolchange dispatches a plain Event. + document.modelContext.ontoolchange = (event: ToolActivatedEvent) => event.toolName; } - expectTypeOf(new ToolActivatedEvent('toolactivated')).toEqualTypeOf(); + const activated = new ToolActivatedEvent('toolactivated'); + expectTypeOf(activated).toEqualTypeOf(); expectTypeOf(new ToolActivatedEvent('toolactivated', { toolName: 'search', bubbles: true })) .toEqualTypeOf(); - expectTypeOf(new ToolCancelEvent('toolcancel', { toolName: 'search' })).toEqualTypeOf(); + expectTypeOf(new ToolCancelEvent('toolcancel', { toolName: 'search', bubbles: true })).toEqualTypeOf(); + + expectTypeOf<{}>().toExtend(); + expectTypeOf<{}>().toExtend(); + const event = new Event('toolactivated'); + if (event instanceof ToolActivatedEvent) { + expectTypeOf(event).toEqualTypeOf(); + } + + // @ts-expect-error The event type is required. + new ToolActivatedEvent(); + // @ts-expect-error toolName is a string. + new ToolCancelEvent('toolcancel', { toolName: 1 }); + // @ts-expect-error toolName is read-only. + activated.toolName = 'other'; expectTypeOf().toExtend(); expectTypeOf().toExtend(); diff --git a/package.json b/package.json index f720de0..957f2ec 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "homepage": "https://github.com/webmachinelearning/webmcp-types", "bugs": "https://github.com/webmachinelearning/webmcp-types/issues", "description": "TypeScript type definitions for WebMCP", - "main": "", "types": "index.d.ts", "files": [ "index.d.ts"