diff --git a/packages/forms/signals/src/webmcp/registration.ts b/packages/forms/signals/src/webmcp/registration.ts index 5653d3c814f..bc38529a879 100644 --- a/packages/forms/signals/src/webmcp/registration.ts +++ b/packages/forms/signals/src/webmcp/registration.ts @@ -55,6 +55,15 @@ async function initWebMcpForm( name: options.name, description: options.description, inputSchema, + annotations: { + // Forms are assumed to implicitly mutate the DOM (otherwise how would a user interact with them?) + // and therefore are _never_ read-only. + readOnlyHint: false, + + // Response text is currently hard-coded by the framework and trusted or derived from application + // errors which are considered trusted. + untrustedContentHint: false, + }, execute: async (args: Record) => { // Populate the form with changes from the agent. node.value.set(args); diff --git a/packages/forms/signals/test/web/webmcp.spec.ts b/packages/forms/signals/test/web/webmcp.spec.ts index b92d763d785..6e6ba789183 100644 --- a/packages/forms/signals/test/web/webmcp.spec.ts +++ b/packages/forms/signals/test/web/webmcp.spec.ts @@ -40,6 +40,9 @@ describe('Signal Forms WebMCP Integration', () => { }, }); + const modelContext = (globalThis.document as any).modelContext; + const registerSpy = spyOn(modelContext, 'registerTool').and.callThrough(); + TestBed.runInInjectionContext(() => { form(model, { experimentalWebMcpTool: { @@ -50,6 +53,16 @@ describe('Signal Forms WebMCP Integration', () => { }); await TestBed.inject(ApplicationRef).whenStable(); + expect(registerSpy).toHaveBeenCalledWith( + jasmine.objectContaining({ + annotations: { + readOnlyHint: false, + untrustedContentHint: false, + }, + }), + jasmine.anything(), + ); + const registeredTools = globalThis.navigator.modelContextTesting!.listTools(); expect(registeredTools[0].name).toBe('testFormTool'); expect(registeredTools[0].description).toBe('A test form tool');