diff --git a/goldens/public-api/forms/signals/index.api.md b/goldens/public-api/forms/signals/index.api.md index ed56a694c3d..08f5b0cdc3d 100644 --- a/goldens/public-api/forms/signals/index.api.md +++ b/goldens/public-api/forms/signals/index.api.md @@ -213,6 +213,9 @@ export interface FormOptions { experimentalWebMcpTool?: { name: string; description: string; + annotations?: { + consequentialHint?: boolean; + }; }; injector?: Injector; name?: string; diff --git a/packages/forms/signals/src/api/structure.ts b/packages/forms/signals/src/api/structure.ts index 65c823920e8..0648b3b03a2 100644 --- a/packages/forms/signals/src/api/structure.ts +++ b/packages/forms/signals/src/api/structure.ts @@ -69,6 +69,20 @@ export interface FormOptions { /** A description of the tool's purpose and usage information. */ description: string; + + /** + * Set of configuration values extending the core tool behavior. + */ + annotations?: { + // `readOnlyHint` and `untrustedContentHint` are intentionally omitted as they have + // hard-coded values. + + /** + * A hint that the tool will have consequential effects like mutating state or interacting + * with the external world. + */ + consequentialHint?: boolean; + }; }; /** Options that define how to handle form submission. */ @@ -232,6 +246,7 @@ export function form(...args: any[]): FieldTree { registerWebMcpForm(fieldRoot.fieldTree, { name: experimentalWebMcpTool.name, description: experimentalWebMcpTool.description, + annotations: experimentalWebMcpTool.annotations, }), ); } else { diff --git a/packages/forms/signals/src/webmcp/registration.ts b/packages/forms/signals/src/webmcp/registration.ts index 69ff2efdd99..a4d7a16d805 100644 --- a/packages/forms/signals/src/webmcp/registration.ts +++ b/packages/forms/signals/src/webmcp/registration.ts @@ -37,7 +37,7 @@ const registerWebMcpForm: RegisterWebMcpForm = (formTree, options) => { async function initWebMcpForm( formTree: FieldTree, - options: {name: string; description: string}, + options: {name: string; description: string; annotations?: {consequentialHint?: boolean}}, injector: Injector, ) { const node = formTree() as FieldNode; @@ -56,6 +56,8 @@ async function initWebMcpForm( description: options.description, inputSchema, annotations: { + ...options.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, diff --git a/packages/forms/signals/src/webmcp/tokens.ts b/packages/forms/signals/src/webmcp/tokens.ts index d198a30c4bc..768fb34491b 100644 --- a/packages/forms/signals/src/webmcp/tokens.ts +++ b/packages/forms/signals/src/webmcp/tokens.ts @@ -22,5 +22,5 @@ export const REGISTER_WEBMCP_FORM = new InjectionToken( */ export type RegisterWebMcpForm = ( form: FieldTree, - options: {name: string; description: string}, + options: {name: string; description: string; annotations?: {consequentialHint?: boolean}}, ) => Promise;