Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 10 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,79 +1,41 @@
# 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).
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.

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.

## 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`
- pnpm: `pnpm add -D webmcp-types`

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
/// <reference types="webmcp-types" />
```

#### 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.
23 changes: 21 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
export {};

type IsUnion<T, TWhole = T> = T extends TWhole ? [TWhole] extends [T] ? false : true : never;
type NonUnionTupleElements<TTuple extends readonly string[]> = {
[TIndex in keyof TTuple]: true extends IsUnion<TTuple[TIndex]> ? never : TTuple[TIndex];
}[number];
type Simplify<T> = { [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> = TSchema extends {
readonly required: infer TRequired extends readonly string[];
} ? number extends TRequired["length"]
Expand Down Expand Up @@ -283,6 +282,7 @@ interface Document {
interface ToolActivatedEventInit extends EventInit {
/**
* The name of the tool whose execution has started.
* @default ""
*/
toolName?: string;
}
Expand All @@ -305,6 +305,7 @@ var ToolActivatedEvent: {
interface ToolCancelEventInit extends EventInit {
/**
* The name of the tool whose execution was cancelled.
* @default ""
*/
toolName?: string;
}
Expand All @@ -323,6 +324,24 @@ 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()`.
*
* @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 during submit event dispatch, after calling `preventDefault()`.
*
* @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<unknown>): void;
}
}

export type { WebMCP };
52 changes: 48 additions & 4 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ test('supports the toolchange, toolactivated, and toolcancel events', () => {
expectTypeOf(event).toEqualTypeOf<Event>();
});

document.modelContext?.addEventListener('toolactivated', (event) => {
document.modelContext?.addEventListener('toolactivated', function (event) {
expectTypeOf(this).toEqualTypeOf<WebMCP.ModelContext>();
expectTypeOf(event).toEqualTypeOf<ToolActivatedEvent>();
expectTypeOf(event.toolName).toEqualTypeOf<string>();
});
Expand All @@ -203,12 +204,14 @@ test('supports the toolchange, toolactivated, and toolcancel events', () => {
expectTypeOf(event).toEqualTypeOf<ToolCancelEvent>();
expectTypeOf(event.toolName).toEqualTypeOf<string>();
});
window.addEventListener('toolactivated', (event) => expectTypeOf(event).toEqualTypeOf<Event>());

if (document.modelContext) {
document.modelContext.ontoolchange = (event) => {
expectTypeOf(event).toEqualTypeOf<Event>();
};
document.modelContext.ontoolactivated = (event) => {
document.modelContext.ontoolactivated = function (event) {
expectTypeOf(this).toEqualTypeOf<WebMCP.ModelContext>();
expectTypeOf(event).toEqualTypeOf<ToolActivatedEvent>();
};
document.modelContext.ontoolcancel = (event) => {
Expand All @@ -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<ToolActivatedEvent>();
const activated = new ToolActivatedEvent('toolactivated');
expectTypeOf(activated).toEqualTypeOf<ToolActivatedEvent>();
expectTypeOf(new ToolActivatedEvent('toolactivated', { toolName: 'search', bubbles: true }))
.toEqualTypeOf<ToolActivatedEvent>();
expectTypeOf(new ToolCancelEvent('toolcancel', { toolName: 'search' })).toEqualTypeOf<ToolCancelEvent>();
expectTypeOf(new ToolCancelEvent('toolcancel', { toolName: 'search', bubbles: true })).toEqualTypeOf<ToolCancelEvent>();

expectTypeOf<{}>().toExtend<ToolActivatedEventInit>();
expectTypeOf<{}>().toExtend<ToolCancelEventInit>();
const event = new Event('toolactivated');
if (event instanceof ToolActivatedEvent) {
expectTypeOf(event).toEqualTypeOf<ToolActivatedEvent>();
}

// @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<ToolActivatedEvent>().toExtend<Event>();
expectTypeOf<ToolCancelEvent>().toExtend<Event>();
Expand All @@ -233,3 +253,27 @@ test('supports the toolchange, toolactivated, and toolcancel events', () => {
expectTypeOf<ToolActivatedEventInit['toolName']>().toEqualTypeOf<string | undefined>();
expectTypeOf<ToolCancelEventInit['toolName']>().toEqualTypeOf<string | undefined>();
});

test("adds the declarative explainer's SubmitEvent members", () => {
document.forms[0].addEventListener('submit', (event) => {
expectTypeOf(event.agentInvoked).toEqualTypeOf<boolean>();
expectTypeOf(event.respondWith).parameters
.toEqualTypeOf<[agentResponse: PromiseLike<unknown>]>();
expectTypeOf(event.respondWith).returns.toEqualTypeOf<void>();

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 });
});
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading