Skip to content

Commit 9dcfec6

Browse files
djgouldclaude
andcommitted
feat(clerk-js): dispatch a clerk:webmcp event for each WebMCP tool call
Apps can listen on window and send agent activity to their own analytics. The event carries the tool name, result status and sign-in strategy, never the identifier or code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 5a2afc1 commit 9dcfec6

3 files changed

Lines changed: 31 additions & 2 deletions

File tree

‎.changeset/experimental-webmcp-tools.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
'@clerk/shared': minor
44
---
55

6-
Add an experimental `webmcp` option (`experimental: { webmcp: true }`). In browsers that support [WebMCP](https://webmachinelearning.github.io/webmcp/), Clerk registers tools that let browser agents check auth state, sign in, submit verification codes, switch organizations and sign out. The tools never take a password or return a token; password and passkey sign-in go through the browser's own prompts.
6+
Add an experimental `webmcp` option (`experimental: { webmcp: true }`). In browsers that support [WebMCP](https://webmachinelearning.github.io/webmcp/), Clerk registers tools that let browser agents check auth state, sign in, submit verification codes, switch organizations and sign out, without ever taking a password or returning a token. Each tool call dispatches a `clerk:webmcp` event on `window` with the tool name, result status and sign-in strategy, so you can track agent activity in your own analytics.

‎packages/clerk-js/src/core/modules/webmcp/__tests__/webmcp.test.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,25 @@ describe('registerWebMcpTools', () => {
159159
expect(setup.clerk.setActive).toHaveBeenCalledWith({ session: 'sess_1' });
160160
});
161161

162+
it('dispatches a clerk:webmcp event for each tool call', async () => {
163+
const { signIn, update } = setup;
164+
const factor = { strategy: 'email_code', emailAddressId: 'idn_1', safeIdentifier: 'j***@acme.com' };
165+
signIn.create.mockImplementation(update({ status: 'needs_first_factor', supportedFirstFactors: [factor] }));
166+
const events: unknown[] = [];
167+
const listener = (event: Event) => events.push((event as CustomEvent).detail);
168+
window.addEventListener('clerk:webmcp', listener);
169+
await register();
170+
171+
await call('clerk_get_auth_state');
172+
await call('clerk_sign_in', { method: 'email_code', identifier: 'jane@acme.com' });
173+
window.removeEventListener('clerk:webmcp', listener);
174+
175+
expect(events).toEqual([
176+
{ tool: 'clerk_get_auth_state', status: 'signed_out' },
177+
{ tool: 'clerk_sign_in', status: 'needs_code', strategy: 'email_code' },
178+
]);
179+
});
180+
162181
it('asks for an authenticator app code when the account has MFA', async () => {
163182
const { signIn, update } = setup;
164183
signIn.status = 'needs_first_factor';

‎packages/clerk-js/src/core/modules/webmcp/index.ts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,19 @@ const toError = (error: unknown): ToolResult => {
4242
};
4343
};
4444

45+
const getStatus = (result: ToolResult) =>
46+
typeof result.status === 'string' ? result.status : result.signedIn ? 'signed_in' : 'signed_out';
47+
4548
const defineTool = (tool: WebMcpTool): WebMcpTool => ({
4649
...tool,
47-
execute: input => tool.execute(input ?? {}).catch(toError),
50+
execute: async input => {
51+
const result = await tool.execute(input ?? {}).catch(toError);
52+
const strategy = typeof input?.method === 'string' ? { strategy: input.method } : {};
53+
window.dispatchEvent(
54+
new CustomEvent('clerk:webmcp', { detail: { tool: tool.name, status: getStatus(result), ...strategy } }),
55+
);
56+
return result;
57+
},
4858
});
4959

5060
const getAppName = (clerk: Clerk) => clerk.__internal_environment?.displayConfig.applicationName || 'this app';

0 commit comments

Comments
 (0)