@copilotkit/vue 1.70.0 → 1.70.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"use-frontend-tool.d.ts","sourceRoot":"","sources":["../../../src/v2/hooks/use-frontend-tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAEvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAKhD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,EACxB,IAAI,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,QA0C9B"}
1
+ {"version":3,"file":"use-frontend-tool.d.ts","sourceRoot":"","sources":["../../../src/v2/hooks/use-frontend-tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAEvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAKhD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,EACxB,IAAI,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,QA6C9B"}
package/dist/v2/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("@copilotkit/core"),i=require("@ag-ui/client"),e=require("../use-render-activity-message-h2NHCkBt.cjs"),n=require("@copilotkit/shared");exports.A2UIActivityContentSchema=e.A2UIActivityContentSchema;exports.A2UISurfaceActivityRenderer=e._sfc_main;exports.A2UISurfaceActivityType=e.A2UISurfaceActivityType;exports.CopilotChat=e.CopilotChat;exports.CopilotChatAssistantMessage=e._sfc_main$1;exports.CopilotChatAttachmentQueue=e._sfc_main$2;exports.CopilotChatAttachmentRenderer=e._sfc_main$3;exports.CopilotChatAudioRecorder=e._sfc_main$4;exports.CopilotChatConfigurationProvider=e._sfc_main$5;exports.CopilotChatDefaultLabels=e.CopilotChatDefaultLabels;exports.CopilotChatInput=e._sfc_main$6;exports.CopilotChatMessageView=e._sfc_main$7;exports.CopilotChatReasoningMessage=e._sfc_main$8;exports.CopilotChatSuggestionPill=e._sfc_main$9;exports.CopilotChatSuggestionView=e._sfc_main$10;exports.CopilotChatToggleButton=e.CopilotChatToggleButton;exports.CopilotChatToggleButtonCloseIcon=e.CopilotChatToggleButtonCloseIcon;exports.CopilotChatToggleButtonOpenIcon=e.CopilotChatToggleButtonOpenIcon;exports.CopilotChatToolCallsView=e._sfc_main$11;exports.CopilotChatUserMessage=e._sfc_main$12;exports.CopilotChatView=e._sfc_main$13;exports.CopilotKitCoreVue=e.CopilotKitCoreVue;exports.CopilotKitInspector=e._sfc_main$14;exports.CopilotKitProvider=e._sfc_main$15;exports.CopilotModalHeader=e.CopilotModalHeader;exports.CopilotPopup=e._sfc_main$16;exports.CopilotPopupView=e.CopilotPopupView;exports.CopilotSidebar=e._sfc_main$17;exports.CopilotSidebarView=e.CopilotSidebarView;exports.CopilotThreadsDrawer=e._sfc_main$18;exports.GenerateSandboxedUiArgsSchema=e.GenerateSandboxedUiArgsSchema;exports.LicenseContextKey=e.LicenseContextKey;exports.MCPAppsActivityContentSchema=e.MCPAppsActivityContentSchema;exports.MCPAppsActivityRenderer=e.MCPAppsActivityRenderer;exports.MCPAppsActivityType=e.MCPAppsActivityType;exports.OpenGenerativeUIActivityRenderer=e.OpenGenerativeUIActivityRenderer;exports.OpenGenerativeUIActivityType=e.OpenGenerativeUIActivityType;exports.OpenGenerativeUIContentSchema=e.OpenGenerativeUIContentSchema;exports.OpenGenerativeUIRenderer=e.OpenGenerativeUIRenderer;exports.OpenGenerativeUIToolRenderer=e.OpenGenerativeUIToolRenderer;exports.UseAgentUpdate=e.UseAgentUpdate;exports.createA2UIMessageRenderer=e.createA2UIMessageRenderer;exports.createDefaultLicenseRef=e.createDefaultLicenseRef;exports.defineToolCallRenderer=e.defineToolCallRenderer;exports.extractCompleteStyles=e.extractCompleteStyles;exports.getOperationSurfaceId=e.getOperationSurfaceId;exports.processPartialHtml=e.processPartialHtml;exports.useAgent=e.useAgent;exports.useAgentContext=e.useAgentContext;exports.useAttachments=e.useAttachments;exports.useCapabilities=e.useCapabilities;exports.useComponent=e.useComponent;exports.useConfigureSuggestions=e.useConfigureSuggestions;exports.useCopilotChatConfiguration=e.useCopilotChatConfiguration;exports.useCopilotKit=e.useCopilotKit;exports.useDefaultRenderTool=e.useDefaultRenderTool;exports.useFrontendTool=e.useFrontendTool;exports.useHumanInTheLoop=e.useHumanInTheLoop;exports.useInterrupt=e.useInterrupt;exports.useKatexStyles=e.useKatexStyles;exports.useKeyboardHeight=e.useKeyboardHeight;exports.useLicenseContext=e.useLicenseContext;exports.useRenderActivityMessage=e.useRenderActivityMessage;exports.useRenderCustomMessages=e.useRenderCustomMessages;exports.useRenderTool=e.useRenderTool;exports.useSandboxFunctions=e.useSandboxFunctions;exports.useSuggestions=e.useSuggestions;exports.useThreads=e.useThreads;exports.vueBasicCatalog=e.vueBasicCatalog;Object.defineProperty(exports,"createLicenseContextValue",{enumerable:!0,get:()=>n.createLicenseContextValue});Object.keys(o).forEach(t=>{t!=="default"&&!Object.prototype.hasOwnProperty.call(exports,t)&&Object.defineProperty(exports,t,{enumerable:!0,get:()=>o[t]})});Object.keys(i).forEach(t=>{t!=="default"&&!Object.prototype.hasOwnProperty.call(exports,t)&&Object.defineProperty(exports,t,{enumerable:!0,get:()=>i[t]})});
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("@copilotkit/core"),i=require("@ag-ui/client"),e=require("../use-render-activity-message-CyThomMH.cjs"),n=require("@copilotkit/shared");exports.A2UIActivityContentSchema=e.A2UIActivityContentSchema;exports.A2UISurfaceActivityRenderer=e._sfc_main;exports.A2UISurfaceActivityType=e.A2UISurfaceActivityType;exports.CopilotChat=e.CopilotChat;exports.CopilotChatAssistantMessage=e._sfc_main$1;exports.CopilotChatAttachmentQueue=e._sfc_main$2;exports.CopilotChatAttachmentRenderer=e._sfc_main$3;exports.CopilotChatAudioRecorder=e._sfc_main$4;exports.CopilotChatConfigurationProvider=e._sfc_main$5;exports.CopilotChatDefaultLabels=e.CopilotChatDefaultLabels;exports.CopilotChatInput=e._sfc_main$6;exports.CopilotChatMessageView=e._sfc_main$7;exports.CopilotChatReasoningMessage=e._sfc_main$8;exports.CopilotChatSuggestionPill=e._sfc_main$9;exports.CopilotChatSuggestionView=e._sfc_main$10;exports.CopilotChatToggleButton=e.CopilotChatToggleButton;exports.CopilotChatToggleButtonCloseIcon=e.CopilotChatToggleButtonCloseIcon;exports.CopilotChatToggleButtonOpenIcon=e.CopilotChatToggleButtonOpenIcon;exports.CopilotChatToolCallsView=e._sfc_main$11;exports.CopilotChatUserMessage=e._sfc_main$12;exports.CopilotChatView=e._sfc_main$13;exports.CopilotKitCoreVue=e.CopilotKitCoreVue;exports.CopilotKitInspector=e._sfc_main$14;exports.CopilotKitProvider=e._sfc_main$15;exports.CopilotModalHeader=e.CopilotModalHeader;exports.CopilotPopup=e._sfc_main$16;exports.CopilotPopupView=e.CopilotPopupView;exports.CopilotSidebar=e._sfc_main$17;exports.CopilotSidebarView=e.CopilotSidebarView;exports.CopilotThreadsDrawer=e._sfc_main$18;exports.GenerateSandboxedUiArgsSchema=e.GenerateSandboxedUiArgsSchema;exports.LicenseContextKey=e.LicenseContextKey;exports.MCPAppsActivityContentSchema=e.MCPAppsActivityContentSchema;exports.MCPAppsActivityRenderer=e.MCPAppsActivityRenderer;exports.MCPAppsActivityType=e.MCPAppsActivityType;exports.OpenGenerativeUIActivityRenderer=e.OpenGenerativeUIActivityRenderer;exports.OpenGenerativeUIActivityType=e.OpenGenerativeUIActivityType;exports.OpenGenerativeUIContentSchema=e.OpenGenerativeUIContentSchema;exports.OpenGenerativeUIRenderer=e.OpenGenerativeUIRenderer;exports.OpenGenerativeUIToolRenderer=e.OpenGenerativeUIToolRenderer;exports.UseAgentUpdate=e.UseAgentUpdate;exports.createA2UIMessageRenderer=e.createA2UIMessageRenderer;exports.createDefaultLicenseRef=e.createDefaultLicenseRef;exports.defineToolCallRenderer=e.defineToolCallRenderer;exports.extractCompleteStyles=e.extractCompleteStyles;exports.getOperationSurfaceId=e.getOperationSurfaceId;exports.processPartialHtml=e.processPartialHtml;exports.useAgent=e.useAgent;exports.useAgentContext=e.useAgentContext;exports.useAttachments=e.useAttachments;exports.useCapabilities=e.useCapabilities;exports.useComponent=e.useComponent;exports.useConfigureSuggestions=e.useConfigureSuggestions;exports.useCopilotChatConfiguration=e.useCopilotChatConfiguration;exports.useCopilotKit=e.useCopilotKit;exports.useDefaultRenderTool=e.useDefaultRenderTool;exports.useFrontendTool=e.useFrontendTool;exports.useHumanInTheLoop=e.useHumanInTheLoop;exports.useInterrupt=e.useInterrupt;exports.useKatexStyles=e.useKatexStyles;exports.useKeyboardHeight=e.useKeyboardHeight;exports.useLicenseContext=e.useLicenseContext;exports.useRenderActivityMessage=e.useRenderActivityMessage;exports.useRenderCustomMessages=e.useRenderCustomMessages;exports.useRenderTool=e.useRenderTool;exports.useSandboxFunctions=e.useSandboxFunctions;exports.useSuggestions=e.useSuggestions;exports.useThreads=e.useThreads;exports.vueBasicCatalog=e.vueBasicCatalog;Object.defineProperty(exports,"createLicenseContextValue",{enumerable:!0,get:()=>n.createLicenseContextValue});Object.keys(o).forEach(t=>{t!=="default"&&!Object.prototype.hasOwnProperty.call(exports,t)&&Object.defineProperty(exports,t,{enumerable:!0,get:()=>o[t]})});Object.keys(i).forEach(t=>{t!=="default"&&!Object.prototype.hasOwnProperty.call(exports,t)&&Object.defineProperty(exports,t,{enumerable:!0,get:()=>i[t]})});
2
2
  //# sourceMappingURL=index.cjs.map
package/dist/v2/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from "@copilotkit/core";
2
2
  export * from "@ag-ui/client";
3
- import { A as o, _ as i, a as n, C, b as r, c as p, d as l, e as u, f as c, g, h as d, i as h, j as A, k as v, l as S, m, n as y, o as I, p as f, q as R, r as T, s as U, t as x, u as M, v as P, w as b, x as V, y as w, z as K, B as O, G, L, M as B, D, E as H, O as F, F as Q, H as j, I as k, J as q, U as z, K as E, N as J, P as N, Q as W, R as X, S as Y, T as Z, V as _, W as $, X as ee, Y as ae, Z as te, $ as se, a0 as oe, a1 as ie, a2 as ne, a3 as Ce, a4 as re, a5 as pe, a6 as le, a7 as ue, a8 as ce, a9 as ge, aa as de, ab as he, ac as Ae, ad as ve, ae as Se } from "../use-render-activity-message-ytoSQi6V.js";
3
+ import { A as o, _ as i, a as n, C, b as r, c as p, d as l, e as u, f as c, g, h as d, i as h, j as A, k as v, l as S, m, n as y, o as I, p as f, q as R, r as T, s as U, t as x, u as M, v as P, w as b, x as V, y as w, z as K, B as O, G, L, M as B, D, E as H, O as F, F as Q, H as j, I as k, J as q, U as z, K as E, N as J, P as N, Q as W, R as X, S as Y, T as Z, V as _, W as $, X as ee, Y as ae, Z as te, $ as se, a0 as oe, a1 as ie, a2 as ne, a3 as Ce, a4 as re, a5 as pe, a6 as le, a7 as ue, a8 as ce, a9 as ge, aa as de, ab as he, ac as Ae, ad as ve, ae as Se } from "../use-render-activity-message-WeN5iF5H.js";
4
4
  import { createLicenseContextValue as ye } from "@copilotkit/shared";
5
5
  export {
6
6
  o as A2UIActivityContentSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@copilotkit/vue",
3
- "version": "1.70.0",
3
+ "version": "1.70.1",
4
4
  "private": false,
5
5
  "description": "Vue 3 components and composables for CopilotKit",
6
6
  "keywords": [
@@ -70,10 +70,10 @@
70
70
  "streamdown-vue": "^1.0.29",
71
71
  "zod": "^3.25.75",
72
72
  "zod-to-json-schema": "^3.24.5",
73
- "@copilotkit/core": "1.70.0",
74
- "@copilotkit/web-components": "1.70.0",
75
- "@copilotkit/web-inspector": "1.70.0",
76
- "@copilotkit/shared": "1.70.0"
73
+ "@copilotkit/core": "1.70.1",
74
+ "@copilotkit/shared": "1.70.1",
75
+ "@copilotkit/web-components": "1.70.1",
76
+ "@copilotkit/web-inspector": "1.70.1"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@eslint/js": "^9.39.2",
@@ -0,0 +1,113 @@
1
+ import { defineComponent, ref, watch } from "vue";
2
+ import { screen, fireEvent, waitFor } from "@testing-library/vue";
3
+ import { describe, it, expect } from "vitest";
4
+ import { useFrontendTool } from "../use-frontend-tool";
5
+ import { useCopilotKit } from "../../v2/providers/useCopilotKit";
6
+ import type { CopilotKitCoreVue } from "../../v2/lib/vue-core";
7
+ import { renderWithCopilotKit } from "../../v2/__tests__/utils/test-helpers";
8
+
9
+ /**
10
+ * Component that captures the copilotkit core ref for test assertions.
11
+ */
12
+ const CoreCapture = defineComponent({
13
+ props: {
14
+ onCore: {
15
+ type: Function as () => (core: CopilotKitCoreVue) => void,
16
+ required: true,
17
+ },
18
+ },
19
+ setup(props) {
20
+ const { copilotkit } = useCopilotKit();
21
+ watch(
22
+ copilotkit,
23
+ (core) => {
24
+ if (core) {
25
+ props.onCore(core);
26
+ }
27
+ },
28
+ { immediate: true },
29
+ );
30
+ return {};
31
+ },
32
+ template: `<div />`,
33
+ });
34
+
35
+ describe("useFrontendTool v1 webmcp pass-through", () => {
36
+ it("re-registers tool when a reactive webmcp getter changes", async () => {
37
+ let coreRef: CopilotKitCoreVue | null = null;
38
+
39
+ const ToolComponent = defineComponent({
40
+ setup() {
41
+ const readOnly = ref(false);
42
+
43
+ useFrontendTool(
44
+ {
45
+ name: "v1WebmcpTool",
46
+ description: "A v1 WebMCP-exposed tool",
47
+ handler: async () => ({ ok: true }),
48
+ // Expose webmcp as a getter over reactive state: the registration
49
+ // must re-read it when it changes, not capture the initial value.
50
+ get webmcp() {
51
+ return { annotations: { readOnlyHint: readOnly.value } };
52
+ },
53
+ },
54
+ [readOnly],
55
+ );
56
+
57
+ return {
58
+ readOnly,
59
+ toggle: () => {
60
+ readOnly.value = !readOnly.value;
61
+ },
62
+ };
63
+ },
64
+ template: `
65
+ <button data-testid="toggle-btn" @click="toggle">
66
+ {{ readOnly ? "Read-write" : "Read-only" }}
67
+ </button>
68
+ `,
69
+ });
70
+
71
+ const Host = defineComponent({
72
+ components: { ToolComponent, CoreCapture },
73
+ setup() {
74
+ return {
75
+ setCore: (core: CopilotKitCoreVue) => {
76
+ coreRef = core;
77
+ },
78
+ };
79
+ },
80
+ template: `
81
+ <div>
82
+ <ToolComponent />
83
+ <CoreCapture :on-core="setCore" />
84
+ </div>
85
+ `,
86
+ });
87
+
88
+ const ui = renderWithCopilotKit({
89
+ children: Host,
90
+ });
91
+
92
+ await waitFor(() => {
93
+ expect(coreRef).not.toBeNull();
94
+ const tool = coreRef!.tools.find(
95
+ (entry) => entry.name === "v1WebmcpTool",
96
+ );
97
+ expect(tool).toBeDefined();
98
+ expect(tool!.webmcp).toEqual({ annotations: { readOnlyHint: false } });
99
+ });
100
+
101
+ await fireEvent.click(screen.getByTestId("toggle-btn"));
102
+
103
+ await waitFor(() => {
104
+ const tool = coreRef!.tools.find(
105
+ (entry) => entry.name === "v1WebmcpTool",
106
+ );
107
+ expect(tool).toBeDefined();
108
+ expect(tool!.webmcp).toEqual({ annotations: { readOnlyHint: true } });
109
+ });
110
+
111
+ ui.unmount();
112
+ });
113
+ });
@@ -5,6 +5,7 @@
5
5
  * v2 composable (useFrontendTool, useHumanInTheLoop, or useRenderTool).
6
6
  */
7
7
  import type { WatchSource } from "vue";
8
+ import type { WebMCPToolConfig } from "@copilotkit/core";
8
9
  import type { Parameter, MappedParameterTypes } from "@copilotkit/shared";
9
10
  import { getZodParameters, parseJson } from "@copilotkit/shared";
10
11
  import { useFrontendTool as useFrontendToolV2 } from "../v2/hooks/use-frontend-tool";
@@ -38,6 +39,12 @@ export interface FrontendAction<T extends Parameter[] | [] = []> {
38
39
  renderAndWaitForResponse?: VueFrontendTool<MappedParameterTypes<T>>["render"];
39
40
  renderAndWait?: VueFrontendTool<MappedParameterTypes<T>>["render"];
40
41
  agentId?: string;
42
+ /**
43
+ * Also expose this action to browser agents through the WebMCP API
44
+ * (`document.modelContext`). `true` uses default annotations;
45
+ * `{ annotations }` provides WebMCP annotations.
46
+ */
47
+ webmcp?: boolean | WebMCPToolConfig;
41
48
  }
42
49
 
43
50
  export interface CatchAllFrontendAction {
@@ -162,6 +169,12 @@ export function useCopilotAction<const T extends Parameter[] | [] = []>(
162
169
  render: wrapRenderWithJsonResult(typedAction.render),
163
170
  available: normalizedAvailable,
164
171
  agentId: typedAction.agentId,
172
+ // Read webmcp at watch time: the action object may expose it as a getter
173
+ // over reactive state, and capturing the initial value here would
174
+ // re-register a stale configuration.
175
+ get webmcp() {
176
+ return typedAction.webmcp;
177
+ },
165
178
  },
166
179
  deps,
167
180
  );
@@ -5,6 +5,7 @@
5
5
  * then delegates to the v2 composable.
6
6
  */
7
7
  import type { WatchSource } from "vue";
8
+ import type { WebMCPToolConfig } from "@copilotkit/core";
8
9
  import {
9
10
  type Parameter,
10
11
  type MappedParameterTypes,
@@ -23,6 +24,12 @@ export interface UseFrontendToolArgs<T extends Parameter[] | [] = []> {
23
24
  available?: "disabled" | "enabled";
24
25
  render?: VueFrontendTool<MappedParameterTypes<T>>["render"];
25
26
  agentId?: string;
27
+ /**
28
+ * Also expose this tool to browser agents through the WebMCP API
29
+ * (`document.modelContext`). `true` uses default annotations;
30
+ * `{ annotations }` provides WebMCP annotations.
31
+ */
32
+ webmcp?: boolean | WebMCPToolConfig;
26
33
  }
27
34
 
28
35
  export function useFrontendTool<const T extends Parameter[] = []>(
@@ -70,6 +77,12 @@ export function useFrontendTool<const T extends Parameter[] = []>(
70
77
  render: normalizedRender,
71
78
  available: available === undefined ? undefined : available !== "disabled",
72
79
  agentId,
80
+ // Read webmcp at watch time: the v1 args may expose it as a getter over
81
+ // reactive state, and capturing the initial value here would re-register
82
+ // a stale configuration.
83
+ get webmcp() {
84
+ return tool.webmcp;
85
+ },
73
86
  },
74
87
  deps,
75
88
  );
@@ -0,0 +1,282 @@
1
+ import { defineComponent, ref, watch } from "vue";
2
+ import { screen, fireEvent, waitFor } from "@testing-library/vue";
3
+ import { z } from "zod";
4
+ import { describe, it, expect } from "vitest";
5
+ import { useFrontendTool } from "../use-frontend-tool";
6
+ import { useCopilotKit } from "../../providers/useCopilotKit";
7
+ import type { VueFrontendTool } from "../../types";
8
+ import type { CopilotKitCoreVue } from "../../lib/vue-core";
9
+ import { renderWithCopilotKit } from "../../__tests__/utils/test-helpers";
10
+
11
+ /**
12
+ * Component that captures the copilotkit core ref for test assertions.
13
+ */
14
+ const CoreCapture = defineComponent({
15
+ props: {
16
+ onCore: {
17
+ type: Function as () => (core: CopilotKitCoreVue) => void,
18
+ required: true,
19
+ },
20
+ },
21
+ setup(props) {
22
+ const { copilotkit } = useCopilotKit();
23
+ watch(
24
+ copilotkit,
25
+ (core) => {
26
+ if (core) {
27
+ props.onCore(core);
28
+ }
29
+ },
30
+ { immediate: true },
31
+ );
32
+ return {};
33
+ },
34
+ template: `<div />`,
35
+ });
36
+
37
+ describe("useFrontendTool webmcp flag", () => {
38
+ it("registers tool with webmcp: true on the core", async () => {
39
+ let coreRef: CopilotKitCoreVue | null = null;
40
+
41
+ const ToolComponent = defineComponent({
42
+ setup() {
43
+ const tool: VueFrontendTool<{ msg: string }> = {
44
+ name: "webmcpTool",
45
+ description: "A WebMCP-exposed tool",
46
+ webmcp: true,
47
+ parameters: z.object({ msg: z.string() }),
48
+ handler: async () => ({ result: "ok" }),
49
+ };
50
+ useFrontendTool(tool);
51
+ return {};
52
+ },
53
+ template: `<div />`,
54
+ });
55
+
56
+ const Host = defineComponent({
57
+ components: { ToolComponent, CoreCapture },
58
+ setup() {
59
+ return {
60
+ setCore: (core: CopilotKitCoreVue) => {
61
+ coreRef = core;
62
+ },
63
+ };
64
+ },
65
+ template: `
66
+ <div>
67
+ <ToolComponent />
68
+ <CoreCapture :on-core="setCore" />
69
+ </div>
70
+ `,
71
+ });
72
+
73
+ const ui = renderWithCopilotKit({
74
+ children: Host,
75
+ });
76
+
77
+ await waitFor(() => {
78
+ expect(coreRef).not.toBeNull();
79
+ const tool = coreRef!.tools.find((entry) => entry.name === "webmcpTool");
80
+ expect(tool).toBeDefined();
81
+ expect(tool!.webmcp).toBe(true);
82
+ });
83
+
84
+ ui.unmount();
85
+ });
86
+
87
+ it("registers tool with webmcp annotations on the core", async () => {
88
+ let coreRef: CopilotKitCoreVue | null = null;
89
+
90
+ const ToolComponent = defineComponent({
91
+ setup() {
92
+ const tool: VueFrontendTool<{ msg: string }> = {
93
+ name: "annotatedTool",
94
+ description: "A WebMCP-exposed tool with annotations",
95
+ webmcp: { annotations: { readOnlyHint: true } },
96
+ parameters: z.object({ msg: z.string() }),
97
+ handler: async () => ({ result: "ok" }),
98
+ };
99
+ useFrontendTool(tool);
100
+ return {};
101
+ },
102
+ template: `<div />`,
103
+ });
104
+
105
+ const Host = defineComponent({
106
+ components: { ToolComponent, CoreCapture },
107
+ setup() {
108
+ return {
109
+ setCore: (core: CopilotKitCoreVue) => {
110
+ coreRef = core;
111
+ },
112
+ };
113
+ },
114
+ template: `
115
+ <div>
116
+ <ToolComponent />
117
+ <CoreCapture :on-core="setCore" />
118
+ </div>
119
+ `,
120
+ });
121
+
122
+ const ui = renderWithCopilotKit({
123
+ children: Host,
124
+ });
125
+
126
+ await waitFor(() => {
127
+ expect(coreRef).not.toBeNull();
128
+ const tool = coreRef!.tools.find(
129
+ (entry) => entry.name === "annotatedTool",
130
+ );
131
+ expect(tool).toBeDefined();
132
+ expect(tool!.webmcp).toEqual({ annotations: { readOnlyHint: true } });
133
+ });
134
+
135
+ ui.unmount();
136
+ });
137
+
138
+ it("re-registers tool when the webmcp config changes", async () => {
139
+ let coreRef: CopilotKitCoreVue | null = null;
140
+
141
+ const ToolWithToggle = defineComponent({
142
+ setup() {
143
+ const readOnly = ref(false);
144
+
145
+ const tool: VueFrontendTool<{ data: string }> = {
146
+ name: "toggleWebmcpTool",
147
+ description: "A tool with a toggleable WebMCP config",
148
+ get webmcp() {
149
+ return { annotations: { readOnlyHint: readOnly.value } };
150
+ },
151
+ parameters: z.object({ data: z.string() }),
152
+ handler: async () => ({ ok: true }),
153
+ };
154
+ useFrontendTool(tool, [readOnly]);
155
+
156
+ return {
157
+ readOnly,
158
+ toggle: () => {
159
+ readOnly.value = !readOnly.value;
160
+ },
161
+ };
162
+ },
163
+ template: `
164
+ <button data-testid="toggle-btn" @click="toggle">
165
+ {{ readOnly ? "Read-write" : "Read-only" }}
166
+ </button>
167
+ `,
168
+ });
169
+
170
+ const Host = defineComponent({
171
+ components: { ToolWithToggle, CoreCapture },
172
+ setup() {
173
+ return {
174
+ setCore: (core: CopilotKitCoreVue) => {
175
+ coreRef = core;
176
+ },
177
+ };
178
+ },
179
+ template: `
180
+ <div>
181
+ <ToolWithToggle />
182
+ <CoreCapture :on-core="setCore" />
183
+ </div>
184
+ `,
185
+ });
186
+
187
+ const ui = renderWithCopilotKit({
188
+ children: Host,
189
+ });
190
+
191
+ await waitFor(() => {
192
+ expect(coreRef).not.toBeNull();
193
+ const tool = coreRef!.tools.find(
194
+ (entry) => entry.name === "toggleWebmcpTool",
195
+ );
196
+ expect(tool).toBeDefined();
197
+ expect(tool!.webmcp).toEqual({ annotations: { readOnlyHint: false } });
198
+ });
199
+
200
+ await fireEvent.click(screen.getByTestId("toggle-btn"));
201
+
202
+ await waitFor(() => {
203
+ const tool = coreRef!.tools.find(
204
+ (entry) => entry.name === "toggleWebmcpTool",
205
+ );
206
+ expect(tool).toBeDefined();
207
+ expect(tool!.webmcp).toEqual({ annotations: { readOnlyHint: true } });
208
+ });
209
+
210
+ ui.unmount();
211
+ });
212
+
213
+ it("registers and cleans up agent-scoped webmcp tools on the core", async () => {
214
+ let coreRef: CopilotKitCoreVue | null = null;
215
+
216
+ const ToolComponent = defineComponent({
217
+ setup() {
218
+ useFrontendTool({
219
+ name: "scopedTool",
220
+ description: "A WebMCP tool scoped to alpha",
221
+ agentId: "alpha",
222
+ webmcp: { annotations: { readOnlyHint: true } },
223
+ handler: async () => ({ ok: true }),
224
+ });
225
+ useFrontendTool({
226
+ name: "scopedTool",
227
+ description: "A WebMCP tool scoped to beta",
228
+ agentId: "beta",
229
+ webmcp: true,
230
+ handler: async () => ({ ok: true }),
231
+ });
232
+ return {};
233
+ },
234
+ template: `<div />`,
235
+ });
236
+
237
+ const Host = defineComponent({
238
+ components: { ToolComponent, CoreCapture },
239
+ setup() {
240
+ return {
241
+ setCore: (core: CopilotKitCoreVue) => {
242
+ coreRef = core;
243
+ },
244
+ };
245
+ },
246
+ template: `
247
+ <div>
248
+ <ToolComponent />
249
+ <CoreCapture :on-core="setCore" />
250
+ </div>
251
+ `,
252
+ });
253
+
254
+ const ui = renderWithCopilotKit({
255
+ children: Host,
256
+ });
257
+
258
+ await waitFor(() => {
259
+ expect(coreRef).not.toBeNull();
260
+ const alphaTool = coreRef!.tools.find(
261
+ (entry) => entry.name === "scopedTool" && entry.agentId === "alpha",
262
+ );
263
+ expect(alphaTool).toBeDefined();
264
+ expect(alphaTool!.webmcp).toEqual({
265
+ annotations: { readOnlyHint: true },
266
+ });
267
+ const betaTool = coreRef!.tools.find(
268
+ (entry) => entry.name === "scopedTool" && entry.agentId === "beta",
269
+ );
270
+ expect(betaTool).toBeDefined();
271
+ expect(betaTool!.webmcp).toBe(true);
272
+ });
273
+
274
+ ui.unmount();
275
+
276
+ await waitFor(() => {
277
+ expect(
278
+ coreRef!.tools.filter((entry) => entry.name === "scopedTool").length,
279
+ ).toBe(0);
280
+ });
281
+ });
282
+ });
@@ -32,6 +32,9 @@ export function useFrontendTool<T extends Record<string, unknown>>(
32
32
  [
33
33
  () => tool.name,
34
34
  () => tool.available,
35
+ // webmcp is commonly an object literal, so compare it by value to avoid
36
+ // re-registering on identity churn while still reacting to real changes.
37
+ () => JSON.stringify(tool.webmcp ?? null),
35
38
  () => extraDeps.length,
36
39
  ...extraDeps,
37
40
  ],