@mcp-b/react-webmcp 5.0.1 → 5.1.0

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.
package/README.md CHANGED
@@ -148,12 +148,21 @@ If a one-shot transport closes, construct a new transport and pass it to
148
148
 
149
149
  ### Provider Hooks
150
150
 
151
- | Hook | Description |
152
- | ----------------------------------------------- | --------------------------------------------------------- |
153
- | `useWebMCP(config, deps?)` | Register a tool with full control over behavior and state |
154
- | `useWebMCPContext(name, description, getValue)` | Simplified hook for read-only context exposure |
155
- | `useWebMCPPrompt(config)` | Register a reusable MCP prompt |
156
- | `useWebMCPResource(config)` | Register an MCP resource |
151
+ | Hook | Description |
152
+ | --------------------------------------------------------- | --------------------------------------------------------- |
153
+ | `useWebMCP(config, deps?)` | Register a tool with full control over behavior and state |
154
+ | `useWebMCPContext(name, description, getValue, options?)` | Simplified hook for read-only context exposure |
155
+ | `useWebMCPPrompt(config)` | Register a reusable MCP prompt |
156
+ | `useWebMCPResource(config)` | Register an MCP resource |
157
+
158
+ All registration hooks support `enabled`, defaulting to `true`. Pass it in the config for
159
+ tools, prompts, and resources, or as the fourth argument (`{ enabled: false }`) to
160
+ `useWebMCPContext`. Disabling unregisters the item; re-enabling registers the latest committed
161
+ configuration. Keep the hook call unconditional.
162
+
163
+ Disabled prompt and resource hooks report `isRegistered: false`. Tool and context hooks retain
164
+ their execution state and local `execute`/`reset` controls. Disabling does not cancel the handler's
165
+ work, though the runtime may reject an in-flight MCP request when its registration is removed.
157
166
 
158
167
  ### Client Hooks
159
168
 
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { InferOutput, InferToolInput, ToolExecutionState, WebMCPConfig, WebMCPReturn, WebMCPReturn as WebMCPReturn$1, useWebMCP } from "usewebmcp";
1
+ import { InferOutput, InferToolInput, ToolExecutionState, WebMCPConfig, WebMCPConfig as WebMCPConfig$1, WebMCPReturn, WebMCPReturn as WebMCPReturn$1, useWebMCP } from "usewebmcp";
2
2
  import { ReactElement, ReactNode } from "react";
3
3
  import { ToolInputSchema, ToolInputSchema as ToolInputSchema$1 } from "@mcp-b/webmcp-polyfill/schema";
4
4
  import { BrowserMcpServer as ModelContextProtocol, PromptDescriptor, PromptDescriptor as PromptDescriptor$1, ResourceDescriptor, ResourceDescriptor as ResourceDescriptor$1 } from "@mcp-b/webmcp-ts-sdk";
@@ -163,14 +163,14 @@ declare function useMcpClient(): McpClientContextValue;
163
163
  type PromptMessage = Awaited<ReturnType<PromptDescriptor$1['get']>>['messages'][number];
164
164
  /** A single entry returned by {@link WebMCPResourceConfig.read}. */
165
165
  type ResourceContents = Awaited<ReturnType<ResourceDescriptor$1['read']>>['contents'][number];
166
- type WebMCPPromptConfig = Pick<PromptDescriptor$1, 'name' | 'description'> & {
166
+ type WebMCPPromptConfig = Pick<PromptDescriptor$1, 'name' | 'description'> & Pick<WebMCPConfig$1, 'enabled'> & {
167
167
  argsSchema?: ToolInputSchema$1;
168
168
  get: (args: Parameters<PromptDescriptor$1['get']>[0]) => MaybePromise<Awaited<ReturnType<PromptDescriptor$1['get']>>>;
169
169
  };
170
170
  interface WebMCPPromptReturn {
171
171
  isRegistered: boolean;
172
172
  }
173
- type WebMCPResourceConfig = ResourceDescriptor$1;
173
+ type WebMCPResourceConfig = ResourceDescriptor$1 & Pick<WebMCPConfig$1, 'enabled'>;
174
174
  type WebMCPResourceReturn = WebMCPPromptReturn;
175
175
  //#endregion
176
176
  //#region src/useWebMCPContext.d.ts
@@ -189,6 +189,7 @@ type WebMCPResourceReturn = WebMCPPromptReturn;
189
189
  * @param name - Unique identifier for the context tool (e.g., 'context_current_post')
190
190
  * @param description - Human-readable description of the context for AI assistants
191
191
  * @param getValue - Function that returns the current context value
192
+ * @param options - Registration options; enabled defaults to true
192
193
  * @returns Tool execution state and control methods
193
194
  *
194
195
  * @public
@@ -237,7 +238,7 @@ type WebMCPResourceReturn = WebMCPPromptReturn;
237
238
  * }
238
239
  * ```
239
240
  */
240
- declare function useWebMCPContext<T>(name: string, description: string, getValue: () => T): WebMCPReturn$1;
241
+ declare function useWebMCPContext<T>(name: string, description: string, getValue: () => T, options?: Pick<WebMCPConfig$1, 'enabled'>): WebMCPReturn$1;
241
242
  //#endregion
242
243
  //#region src/useWebMCPPrompt.d.ts
243
244
  /**
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- "use client";import{useWebMCP as e,useWebMCP as t}from"usewebmcp";import{createContext as n,useCallback as r,useContext as i,useEffect as a,useLayoutEffect as o,useMemo as s,useRef as c,useState as l}from"react";import{jsx as u}from"react/jsx-runtime";import{normalizeInputSchema as d}from"@mcp-b/webmcp-polyfill/schema";import{isBrowserMcpServer as f}from"@mcp-b/webmcp-ts-sdk";const p=typeof window>`u`?a:o;function m(e){let t=c(e);return p(()=>{t.current=e},[e]),t}const h=n(null);function g(e,t,n){let r=new AbortController;return e.listen(t,{signal:r.signal}).then(async e=>{if(r.signal.aborted){await e.close();return}await n()}).catch(e=>{r.signal.aborted||console.error(`[ReactWebMCP:McpClientProvider]`,`Failed to listen for list_changed notifications:`,e)}),()=>r.abort()}function _({children:e,client:t,transport:n,opts:i}){let[o,d]=l([]),[f,p]=l([]),[_,v]=l(null),[y,b]=l(`disconnected`),[x,S]=l(null),C=y===`initializing`||y===`connected`,w=y===`connecting`||y===`initializing`,T=c(`disconnected`),E=c(0),D=c(null),O=c(null),k=c(0),A=m(i),j=r(async e=>{let n=++k.current,r=t.getServerCapabilities();try{let[i,a]=await Promise.all([r?.resources?t.listResources(void 0,{cacheMode:`refresh`}):void 0,r?.tools?t.listTools(void 0,{cacheMode:`refresh`}):void 0]);if(e!==E.current||n!==k.current)return;d(i?.resources??[]),p(a?.tools??[]),v(null)}catch(t){let r=t instanceof Error?t:Error(String(t));if(e!==E.current||n!==k.current)return;throw v(r),r}},[t]),M=r(async e=>{if(T.current===`connected`&&e===void 0){let e=E.current;T.current=`initializing`,b(`initializing`);try{await j(e)}catch{}e===E.current&&(T.current=`connected`,b(`connected`));return}if(T.current!==`disconnected`)return;T.current=`connecting`,b(`connecting`),v(null);let r=E.current;try{if(await D.current,r!==E.current||(await t.connect(e??n,A.current),r!==E.current))return}catch(e){let t=e instanceof Error?e:Error(String(e));throw r===E.current&&(T.current=`disconnected`,b(`disconnected`),v(t)),t}S(t.getServerCapabilities()??null),T.current=`initializing`,b(`initializing`);try{await j(r)}catch{}r===E.current&&(T.current=`connected`,b(`connected`))},[t,n,j,A]);a(()=>{if(!C)return;let e=t.getServerCapabilities(),n=e?.resources?.listChanged===!0,r=e?.tools?.listChanged===!0,i=async()=>{try{await j(E.current)}catch(e){console.error(`[ReactWebMCP:McpClientProvider]`,`Failed to refresh tools/resources after list_changed:`,e)}};n&&t.setNotificationHandler(`notifications/resources/list_changed`,i),r&&t.setNotificationHandler(`notifications/tools/list_changed`,i);let a=n||r,o=t.getProtocolEra()===`modern`&&a?g(t,{...r&&{toolsListChanged:!0},...n&&{resourcesListChanged:!0}},i):void 0;return!o&&a&&i(),()=>{n&&t.removeNotificationHandler(`notifications/resources/list_changed`),r&&t.removeNotificationHandler(`notifications/tools/list_changed`),o?.()}},[t,C,j]),a(()=>{let e=!0,n=E.current,r=t.onclose,i=()=>{try{r?.()}finally{e&&O.current===null&&(E.current+=1,k.current+=1,T.current=`disconnected`,b(`disconnected`),S(null),d([]),p([]))}};return t.onclose=i,T.current=`disconnected`,b(`disconnected`),M().catch(e=>{n===E.current&&console.error(`[ReactWebMCP:McpClientProvider]`,`Failed to connect MCP client:`,e)}),()=>{e=!1,t.onclose===i&&(r?t.onclose=r:Reflect.deleteProperty(t,`onclose`)),E.current+=1,k.current+=1,T.current=`disconnected`;let n={};O.current=n,D.current=t.close().catch(e=>{console.error(`[ReactWebMCP:McpClientProvider]`,`Failed to close MCP client:`,e)}).finally(()=>{O.current===n&&(O.current=null)})}},[t,n,M]);let N=s(()=>({client:t,tools:f,resources:o,isConnected:C,isLoading:w,error:_,capabilities:x,reconnect:M}),[t,f,o,C,w,_,x,M]);return u(h.Provider,{value:N,children:e})}function v(){let e=i(h);if(!e)throw Error(`useMcpClient must be used within an McpClientProvider`);return e}function y(e,n,r){return t({name:e,description:n,annotations:{title:`Context: ${e}`,readOnlyHint:!0,idempotentHint:!0,destructiveHint:!1,openWorldHint:!1},execute:r})}function b(){if(typeof document>`u`||typeof navigator>`u`)return;let e=document.modelContext??navigator.modelContext;return e&&f(e)?e:void 0}function x(e){let[t,n]=l(!1);return a(()=>{let t=e();if(!t){n(!1);return}return n(!0),()=>{t.unregister()}},[e]),t}function S(e){let{name:t,description:n}=e,i=m(e);return{isRegistered:x(r(()=>{let e=b();if(!e){console.warn(`[ReactWebMCP] BrowserMcpServer is not available. Prompt "${t}" will not be registered.`);return}let{argsSchema:r}=i.current,a=r?d(r).inputSchema:void 0;return e.registerPrompt({name:t,...n!==void 0&&{description:n},...a&&{argsSchema:a},get:async e=>i.current.get(e)})},[t,n,i]))}}function C(e){let{uri:t,name:n,description:i,mimeType:a,read:o}=e,s=m(o);return{isRegistered:x(r(()=>{let e=b();if(!e){console.warn(`[ReactWebMCP] BrowserMcpServer is not available. Resource "${t}" will not be registered.`);return}return e.registerResource({uri:t,name:n,...i!==void 0&&{description:i},...a!==void 0&&{mimeType:a},read:(e,t)=>s.current(e,t)})},[t,n,i,a,s]))}}export{_ as McpClientProvider,v as useMcpClient,e as useWebMCP,y as useWebMCPContext,S as useWebMCPPrompt,C as useWebMCPResource};
1
+ "use client";import{useWebMCP as e,useWebMCP as t}from"usewebmcp";import{createContext as n,useCallback as r,useContext as i,useEffect as a,useLayoutEffect as o,useMemo as s,useRef as c,useState as l}from"react";import{jsx as u}from"react/jsx-runtime";import{normalizeInputSchema as d}from"@mcp-b/webmcp-polyfill/schema";import{isBrowserMcpServer as f}from"@mcp-b/webmcp-ts-sdk";const p=typeof window>`u`?a:o;function m(e){let t=c(e);return p(()=>{t.current=e},[e]),t}const h=n(null);function g(e,t,n){let r=new AbortController;return e.listen(t,{signal:r.signal}).then(async e=>{if(r.signal.aborted){await e.close();return}await n()}).catch(e=>{r.signal.aborted||console.error(`[ReactWebMCP:McpClientProvider]`,`Failed to listen for list_changed notifications:`,e)}),()=>r.abort()}function _({children:e,client:t,transport:n,opts:i}){let[o,d]=l([]),[f,p]=l([]),[_,v]=l(null),[y,b]=l(`disconnected`),[x,S]=l(null),C=y===`initializing`||y===`connected`,w=y===`connecting`||y===`initializing`,T=c(`disconnected`),E=c(0),D=c(null),O=c(null),k=c(0),A=m(i),j=r(async e=>{let n=++k.current,r=t.getServerCapabilities();try{let[i,a]=await Promise.all([r?.resources?t.listResources(void 0,{cacheMode:`refresh`}):void 0,r?.tools?t.listTools(void 0,{cacheMode:`refresh`}):void 0]);if(e!==E.current||n!==k.current)return;d(i?.resources??[]),p(a?.tools??[]),v(null)}catch(t){let r=t instanceof Error?t:Error(String(t));if(e!==E.current||n!==k.current)return;throw v(r),r}},[t]),M=r(async e=>{if(T.current===`connected`&&e===void 0){let e=E.current;T.current=`initializing`,b(`initializing`);try{await j(e)}catch{}e===E.current&&(T.current=`connected`,b(`connected`));return}if(T.current!==`disconnected`)return;T.current=`connecting`,b(`connecting`),v(null);let r=E.current;try{if(await D.current,r!==E.current||(await t.connect(e??n,A.current),r!==E.current))return}catch(e){let t=e instanceof Error?e:Error(String(e));throw r===E.current&&(T.current=`disconnected`,b(`disconnected`),v(t)),t}S(t.getServerCapabilities()??null),T.current=`initializing`,b(`initializing`);try{await j(r)}catch{}r===E.current&&(T.current=`connected`,b(`connected`))},[t,n,j,A]);a(()=>{if(!C)return;let e=t.getServerCapabilities(),n=e?.resources?.listChanged===!0,r=e?.tools?.listChanged===!0,i=async()=>{try{await j(E.current)}catch(e){console.error(`[ReactWebMCP:McpClientProvider]`,`Failed to refresh tools/resources after list_changed:`,e)}};n&&t.setNotificationHandler(`notifications/resources/list_changed`,i),r&&t.setNotificationHandler(`notifications/tools/list_changed`,i);let a=n||r,o=t.getProtocolEra()===`modern`&&a?g(t,{...r&&{toolsListChanged:!0},...n&&{resourcesListChanged:!0}},i):void 0;return!o&&a&&i(),()=>{n&&t.removeNotificationHandler(`notifications/resources/list_changed`),r&&t.removeNotificationHandler(`notifications/tools/list_changed`),o?.()}},[t,C,j]),a(()=>{let e=!0,n=E.current,r=t.onclose,i=()=>{try{r?.()}finally{e&&O.current===null&&(E.current+=1,k.current+=1,T.current=`disconnected`,b(`disconnected`),S(null),d([]),p([]))}};return t.onclose=i,T.current=`disconnected`,b(`disconnected`),S(null),d([]),p([]),M().catch(e=>{n===E.current&&console.error(`[ReactWebMCP:McpClientProvider]`,`Failed to connect MCP client:`,e)}),()=>{e=!1,t.onclose===i&&(r?t.onclose=r:Reflect.deleteProperty(t,`onclose`)),E.current+=1,k.current+=1,T.current=`disconnected`;let n={};O.current=n,D.current=t.close().catch(e=>{console.error(`[ReactWebMCP:McpClientProvider]`,`Failed to close MCP client:`,e)}).finally(()=>{O.current===n&&(O.current=null)})}},[t,n,M]);let N=s(()=>({client:t,tools:f,resources:o,isConnected:C,isLoading:w,error:_,capabilities:x,reconnect:M}),[t,f,o,C,w,_,x,M]);return u(h.Provider,{value:N,children:e})}function v(){let e=i(h);if(!e)throw Error(`useMcpClient must be used within an McpClientProvider`);return e}function y(e,n,r,i={}){return t({...i,name:e,description:n,annotations:{title:`Context: ${e}`,readOnlyHint:!0,idempotentHint:!0,destructiveHint:!1,openWorldHint:!1},execute:r})}function b(){if(typeof document>`u`||typeof navigator>`u`)return;let e=document.modelContext??navigator.modelContext;return e&&f(e)?e:void 0}function x(e,t=!0){let[n,r]=l(!1);return a(()=>{let n=t?e():void 0;if(!n){r(!1);return}return r(!0),()=>{n.unregister()}},[e,t]),n}function S(e){let{name:t,description:n}=e,i=m(e);return{isRegistered:x(r(()=>{let e=b();if(!e){console.warn(`[ReactWebMCP] BrowserMcpServer is not available. Prompt "${t}" will not be registered.`);return}let{argsSchema:r}=i.current,a=r?d(r).inputSchema:void 0;return e.registerPrompt({name:t,...n!==void 0&&{description:n},...a&&{argsSchema:a},get:async e=>i.current.get(e)})},[t,n,i]),e.enabled)}}function C(e){let{uri:t,name:n,description:i,mimeType:a,read:o}=e,s=m(o);return{isRegistered:x(r(()=>{let e=b();if(!e){console.warn(`[ReactWebMCP] BrowserMcpServer is not available. Resource "${t}" will not be registered.`);return}return e.registerResource({uri:t,name:n,...i!==void 0&&{description:i},...a!==void 0&&{mimeType:a},read:(e,t)=>s.current(e,t)})},[t,n,i,a,s]),e.enabled)}}export{_ as McpClientProvider,v as useMcpClient,e as useWebMCP,y as useWebMCPContext,S as useWebMCPPrompt,C as useWebMCPResource};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["useWebMCP"],"sources":["../src/useCommittedRef.ts","../src/client/McpClientProvider.tsx","../src/useWebMCPContext.ts","../src/model-context.ts","../src/useMcpRegistration.ts","../src/useWebMCPPrompt.ts","../src/useWebMCPResource.ts"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef } from 'react';\n\nconst useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;\n\nexport function useCommittedRef<T>(value: T) {\n const ref = useRef(value);\n\n // External MCP work may read values after commit but before passive effects. SSR cannot.\n useIsomorphicLayoutEffect(() => {\n ref.current = value;\n }, [value]);\n\n return ref;\n}\n","import {\n type Client,\n type ConnectOptions,\n type Resource,\n type ServerCapabilities,\n type SubscriptionFilter,\n type Tool as McpTool,\n type Transport,\n} from '@modelcontextprotocol/client';\nimport {\n createContext,\n type ReactElement,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { useCommittedRef } from '../useCommittedRef.js';\n\n/**\n * Context value provided by McpClientProvider.\n *\n * @internal\n */\ninterface McpClientContextValue {\n client: Client;\n tools: McpTool[];\n resources: Resource[];\n isConnected: boolean;\n isLoading: boolean;\n error: Error | null;\n capabilities: ServerCapabilities | null;\n reconnect: (freshTransport?: Transport) => Promise<void>;\n}\n\ntype ConnectionState = 'disconnected' | 'connecting' | 'initializing' | 'connected';\n\nconst McpClientContext = createContext<McpClientContextValue | null>(null);\n\nfunction startListChangedSubscription(\n client: Client,\n filter: SubscriptionFilter,\n refreshLists: () => Promise<void>\n): () => void {\n const controller = new AbortController();\n\n void client\n .listen(filter, { signal: controller.signal })\n .then(async (subscription) => {\n if (controller.signal.aborted) {\n await subscription.close();\n return;\n }\n\n // Modern servers deliver list_changed only after listen is acknowledged.\n await refreshLists();\n })\n .catch((error) => {\n if (!controller.signal.aborted) {\n console.error(\n '[ReactWebMCP:McpClientProvider]',\n 'Failed to listen for list_changed notifications:',\n error\n );\n }\n });\n\n return () => controller.abort();\n}\n\n/**\n * Props for the McpClientProvider component.\n *\n * @public\n */\nexport interface McpClientProviderProps {\n /**\n * React children to render within the provider.\n */\n children: ReactNode;\n\n /**\n * MCP Client instance to use for communication.\n */\n client: Client;\n\n /**\n * Transport instance for the client to connect through.\n */\n transport: Transport;\n\n /**\n * Optional connection options.\n */\n opts?: ConnectOptions;\n}\n\n/**\n * Provider component that manages an MCP client connection and exposes\n * tools, resources, and connection state to child components.\n *\n * This provider handles:\n * - Establishing and maintaining the MCP client connection\n * - Fetching available tools and resources from the server\n * - Listening for server notifications about tool/resource changes\n * - Managing connection state and errors\n * - Automatic cleanup on unmount\n *\n * @param props - Component props\n * @returns Provider component wrapping children\n *\n * @public\n *\n * @example\n * Connect to an MCP server via tab transport:\n * ```tsx\n * import { TabClientTransport } from '@mcp-b/transports';\n * import { McpClientProvider } from '@mcp-b/react-webmcp';\n * import { Client } from '@modelcontextprotocol/client';\n *\n * const client = new Client(\n * { name: 'my-app', version: '1.0.0' },\n * { versionNegotiation: { mode: 'auto' } }\n * );\n *\n * const transport = new TabClientTransport({\n * channelId: 'mcp',\n * targetOrigin: window.location.origin,\n * });\n *\n * function App() {\n * return (\n * <McpClientProvider client={client} transport={transport}>\n * <MyAppContent />\n * </McpClientProvider>\n * );\n * }\n * ```\n *\n * @example\n * Access tools from child components:\n * ```tsx\n * function MyAppContent() {\n * const { tools, isConnected, isLoading } = useMcpClient();\n *\n * if (isLoading) {\n * return <div>Connecting to MCP server...</div>;\n * }\n *\n * if (!isConnected) {\n * return <div>Failed to connect to MCP server</div>;\n * }\n *\n * return (\n * <div>\n * <h2>Available Tools:</h2>\n * <ul>\n * {tools.map(tool => (\n * <li key={tool.name}>{tool.description}</li>\n * ))}\n * </ul>\n * </div>\n * );\n * }\n * ```\n */\nexport function McpClientProvider({\n children,\n client,\n transport,\n opts,\n}: McpClientProviderProps): ReactElement {\n const [resources, setResources] = useState<Resource[]>([]);\n const [tools, setTools] = useState<McpTool[]>([]);\n const [error, setError] = useState<Error | null>(null);\n const [connectionState, setConnectionState] = useState<ConnectionState>('disconnected');\n const [capabilities, setCapabilities] = useState<ServerCapabilities | null>(null);\n const isConnected = connectionState === 'initializing' || connectionState === 'connected';\n const isLoading = connectionState === 'connecting' || connectionState === 'initializing';\n const connectionStateRef = useRef<ConnectionState>('disconnected');\n const connectionGenerationRef = useRef(0);\n const closePromiseRef = useRef<Promise<void> | null>(null);\n const providerCloseRef = useRef<object | null>(null);\n const inventoryRequestRef = useRef(0);\n const requestOptsRef = useCommittedRef(opts);\n\n /**\n * Refreshes every provider-owned inventory list and clears a prior inventory\n * error only after the complete refresh succeeds.\n */\n const refreshInventory = useCallback(\n async (connectionGeneration: number): Promise<void> => {\n const inventoryRequest = ++inventoryRequestRef.current;\n const serverCapabilities = client.getServerCapabilities();\n\n try {\n const [resourceResponse, toolResponse] = await Promise.all([\n serverCapabilities?.resources\n ? client.listResources(undefined, { cacheMode: 'refresh' })\n : undefined,\n serverCapabilities?.tools\n ? client.listTools(undefined, { cacheMode: 'refresh' })\n : undefined,\n ]);\n\n if (\n connectionGeneration !== connectionGenerationRef.current ||\n inventoryRequest !== inventoryRequestRef.current\n ) {\n return;\n }\n\n setResources(resourceResponse?.resources ?? []);\n setTools(toolResponse?.tools ?? []);\n setError(null);\n } catch (cause) {\n const error = cause instanceof Error ? cause : new Error(String(cause));\n if (\n connectionGeneration !== connectionGenerationRef.current ||\n inventoryRequest !== inventoryRequestRef.current\n ) {\n return;\n }\n setError(error);\n throw error;\n }\n },\n [client]\n );\n\n /**\n * Connects a disconnected client, or retries inventory discovery when the\n * MCP handshake is already alive. Calls made while work is in progress no-op.\n *\n * Pass a fresh transport after a one-shot transport has closed.\n */\n const reconnect = useCallback(\n async (freshTransport?: Transport): Promise<void> => {\n if (connectionStateRef.current === 'connected' && freshTransport === undefined) {\n const connectionGeneration = connectionGenerationRef.current;\n connectionStateRef.current = 'initializing';\n setConnectionState('initializing');\n\n try {\n await refreshInventory(connectionGeneration);\n } catch {\n // Inventory failure does not undo the completed MCP handshake.\n }\n\n if (connectionGeneration === connectionGenerationRef.current) {\n connectionStateRef.current = 'connected';\n setConnectionState('connected');\n }\n return;\n }\n\n if (connectionStateRef.current !== 'disconnected') {\n return;\n }\n\n connectionStateRef.current = 'connecting';\n setConnectionState('connecting');\n setError(null);\n const connectionGeneration = connectionGenerationRef.current;\n\n try {\n await closePromiseRef.current;\n if (connectionGeneration !== connectionGenerationRef.current) {\n return;\n }\n await client.connect(freshTransport ?? transport, requestOptsRef.current);\n if (connectionGeneration !== connectionGenerationRef.current) {\n return;\n }\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n if (connectionGeneration === connectionGenerationRef.current) {\n connectionStateRef.current = 'disconnected';\n setConnectionState('disconnected');\n setError(err);\n }\n throw err;\n }\n\n const caps = client.getServerCapabilities();\n setCapabilities(caps ?? null);\n connectionStateRef.current = 'initializing';\n setConnectionState('initializing');\n\n try {\n await refreshInventory(connectionGeneration);\n } catch {\n // Inventory failure does not undo the completed MCP handshake.\n }\n\n if (connectionGeneration === connectionGenerationRef.current) {\n connectionStateRef.current = 'connected';\n setConnectionState('connected');\n }\n },\n [client, transport, refreshInventory, requestOptsRef]\n );\n\n useEffect(() => {\n if (!isConnected) {\n return;\n }\n\n const serverCapabilities = client.getServerCapabilities();\n\n const resourcesListChanged = serverCapabilities?.resources?.listChanged === true;\n const toolsListChanged = serverCapabilities?.tools?.listChanged === true;\n const refreshLists = async () => {\n try {\n await refreshInventory(connectionGenerationRef.current);\n } catch (error) {\n console.error(\n '[ReactWebMCP:McpClientProvider]',\n 'Failed to refresh tools/resources after list_changed:',\n error\n );\n }\n };\n\n if (resourcesListChanged) {\n client.setNotificationHandler('notifications/resources/list_changed', refreshLists);\n }\n\n if (toolsListChanged) {\n client.setNotificationHandler('notifications/tools/list_changed', refreshLists);\n }\n\n const hasListChanged = resourcesListChanged || toolsListChanged;\n const stopListening =\n client.getProtocolEra() === 'modern' && hasListChanged\n ? startListChangedSubscription(\n client,\n {\n ...(toolsListChanged && { toolsListChanged: true }),\n ...(resourcesListChanged && { resourcesListChanged: true }),\n },\n refreshLists\n )\n : undefined;\n if (!stopListening && hasListChanged) {\n // Legacy servers deliver list_changed unsolicited once handlers are installed.\n void refreshLists();\n }\n\n return () => {\n if (resourcesListChanged) {\n client.removeNotificationHandler('notifications/resources/list_changed');\n }\n\n if (toolsListChanged) {\n client.removeNotificationHandler('notifications/tools/list_changed');\n }\n\n stopListening?.();\n };\n }, [client, isConnected, refreshInventory]);\n\n useEffect(() => {\n let active = true;\n const connectionGeneration = connectionGenerationRef.current;\n const previousOnclose = client.onclose;\n const handleClientClose = () => {\n try {\n previousOnclose?.();\n } finally {\n if (active && providerCloseRef.current === null) {\n connectionGenerationRef.current += 1;\n inventoryRequestRef.current += 1;\n connectionStateRef.current = 'disconnected';\n setConnectionState('disconnected');\n setCapabilities(null);\n setResources([]);\n setTools([]);\n }\n }\n };\n client.onclose = handleClientClose;\n connectionStateRef.current = 'disconnected';\n setConnectionState('disconnected');\n\n // Initial connection - reconnect() has its own guard to prevent concurrent connections\n reconnect().catch((err) => {\n if (connectionGeneration === connectionGenerationRef.current) {\n console.error('[ReactWebMCP:McpClientProvider]', 'Failed to connect MCP client:', err);\n }\n });\n\n return () => {\n active = false;\n if (client.onclose === handleClientClose) {\n if (previousOnclose) {\n client.onclose = previousOnclose;\n } else {\n Reflect.deleteProperty(client, 'onclose');\n }\n }\n connectionGenerationRef.current += 1;\n inventoryRequestRef.current += 1;\n connectionStateRef.current = 'disconnected';\n const closeToken = {};\n providerCloseRef.current = closeToken;\n closePromiseRef.current = client\n .close()\n .catch((error: unknown) => {\n console.error('[ReactWebMCP:McpClientProvider]', 'Failed to close MCP client:', error);\n })\n .finally(() => {\n if (providerCloseRef.current === closeToken) {\n providerCloseRef.current = null;\n }\n });\n };\n }, [client, transport, reconnect]);\n\n const contextValue = useMemo(\n () => ({\n client,\n tools,\n resources,\n isConnected,\n isLoading,\n error,\n capabilities,\n reconnect,\n }),\n [client, tools, resources, isConnected, isLoading, error, capabilities, reconnect]\n );\n\n return <McpClientContext.Provider value={contextValue}>{children}</McpClientContext.Provider>;\n}\n\n/**\n * Hook to access the MCP client context.\n * Must be used within an {@link McpClientProvider}.\n *\n * @returns The MCP client context including client instance, tools, resources, and connection state\n * @throws Error if used outside of McpClientProvider\n *\n * @public\n *\n * @example\n * ```tsx\n * function ToolsList() {\n * const { tools, isConnected, error, reconnect } = useMcpClient();\n *\n * if (error) {\n * return (\n * <div>\n * Error: {error.message}\n * <button onClick={() => void reconnect()}>Retry</button>\n * </div>\n * );\n * }\n *\n * if (!isConnected) {\n * return <div>Not connected</div>;\n * }\n *\n * return (\n * <ul>\n * {tools.map(tool => (\n * <li key={tool.name}>{tool.description}</li>\n * ))}\n * </ul>\n * );\n * }\n * ```\n */\nexport function useMcpClient() {\n const context = useContext(McpClientContext);\n if (!context) {\n throw new Error('useMcpClient must be used within an McpClientProvider');\n }\n return context;\n}\n","import { useWebMCP, type WebMCPReturn } from 'usewebmcp';\n\n/**\n * Convenience hook for exposing read-only context data to AI assistants.\n *\n * This is a simplified wrapper around {@link useWebMCP} specifically designed for\n * context tools that expose data without performing actions. The hook automatically\n * configures appropriate annotations (read-only, idempotent) and handles value\n * serialization.\n *\n * JSON-compatible values are normalized to text plus `structuredContent`.\n *\n * @template T - The type of context data to expose\n *\n * @param name - Unique identifier for the context tool (e.g., 'context_current_post')\n * @param description - Human-readable description of the context for AI assistants\n * @param getValue - Function that returns the current context value\n * @returns Tool execution state and control methods\n *\n * @public\n *\n * @example\n * Expose current post context:\n * ```tsx\n * function PostDetailPage() {\n * const { postId } = useParams();\n * const { data: post } = useQuery(['post', postId], () => fetchPost(postId));\n *\n * useWebMCPContext(\n * 'context_current_post',\n * 'Get the currently viewed post ID and metadata',\n * () => ({\n * postId,\n * title: post?.title,\n * author: post?.author,\n * tags: post?.tags,\n * createdAt: post?.createdAt,\n * })\n * );\n *\n * return <PostContent post={post} />;\n * }\n * ```\n *\n * @example\n * Expose user session context:\n * ```tsx\n * function AppRoot() {\n * const { user, isAuthenticated } = useAuth();\n *\n * useWebMCPContext(\n * 'context_user_session',\n * 'Get the current user session information',\n * () => ({\n * isAuthenticated,\n * userId: user?.id,\n * email: user?.email,\n * permissions: user?.permissions,\n * })\n * );\n *\n * return <App />;\n * }\n * ```\n */\nexport function useWebMCPContext<T>(\n name: string,\n description: string,\n getValue: () => T\n): WebMCPReturn {\n return useWebMCP({\n name,\n description,\n annotations: {\n title: `Context: ${name}`,\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: false,\n },\n execute: getValue,\n });\n}\n","import { isBrowserMcpServer, type BrowserMcpServer } from '@mcp-b/webmcp-ts-sdk';\n\nexport function getBrowserMcpServer(): BrowserMcpServer | undefined {\n if (typeof document === 'undefined' || typeof navigator === 'undefined') {\n return undefined;\n }\n\n const modelContext = document.modelContext ?? navigator.modelContext;\n return modelContext && isBrowserMcpServer(modelContext) ? modelContext : undefined;\n}\n","import type { RegistrationHandle } from '@mcp-b/webmcp-types';\nimport { useEffect, useState } from 'react';\n\nexport function useMcpRegistration(register: () => RegistrationHandle | undefined): boolean {\n const [isRegistered, setIsRegistered] = useState(false);\n\n useEffect(() => {\n const registration = register();\n if (!registration) {\n setIsRegistered(false);\n return;\n }\n\n setIsRegistered(true);\n return () => {\n registration.unregister();\n };\n }, [register]);\n\n return isRegistered;\n}\n","import { normalizeInputSchema } from '@mcp-b/webmcp-polyfill/schema';\nimport { useCallback } from 'react';\nimport type { WebMCPPromptConfig, WebMCPPromptReturn } from './types.js';\nimport { getBrowserMcpServer } from './model-context.js';\nimport { useCommittedRef } from './useCommittedRef.js';\nimport { useMcpRegistration } from './useMcpRegistration.js';\n\n/**\n * React hook for registering Model Context Protocol (MCP) prompts.\n *\n * This hook handles the complete lifecycle of an MCP prompt:\n * - Registers the prompt with the installed MCP-B `BrowserMcpServer`\n * - Automatically unregisters on component unmount\n *\n * @param config - Configuration object for the prompt\n * @returns Object indicating registration status\n *\n * @public\n *\n * @example\n * Simple prompt without arguments:\n * ```tsx\n * function HelpPrompt() {\n * const { isRegistered } = useWebMCPPrompt({\n * name: 'help',\n * description: 'Get help with using the application',\n * get: async () => ({\n * messages: [{\n * role: 'user',\n * content: { type: 'text', text: 'How do I use this application?' }\n * }]\n * }),\n * });\n *\n * return <div>Help prompt {isRegistered ? 'ready' : 'loading'}</div>;\n * }\n * ```\n *\n * @example\n * Prompt with typed arguments:\n * ```tsx\n * function CodeReviewPrompt() {\n * const { isRegistered } = useWebMCPPrompt({\n * name: 'review_code',\n * description: 'Review code for best practices',\n * argsSchema: {\n * type: 'object',\n * properties: {\n * code: { type: 'string', description: 'The code to review' },\n * language: { type: 'string', description: 'Programming language' },\n * },\n * required: ['code'],\n * } as const,\n * get: async ({ code, language }) => ({\n * messages: [{\n * role: 'user',\n * content: {\n * type: 'text',\n * text: `Please review this ${language ?? ''} code:\\n\\n${code}`\n * }\n * }]\n * }),\n * });\n *\n * return <div>Code review prompt {isRegistered ? 'ready' : 'loading'}</div>;\n * }\n * ```\n */\nexport function useWebMCPPrompt(config: WebMCPPromptConfig): WebMCPPromptReturn {\n const { name, description } = config;\n\n const configRef = useCommittedRef(config);\n\n const register = useCallback(() => {\n const modelContext = getBrowserMcpServer();\n if (!modelContext) {\n console.warn(\n `[ReactWebMCP] BrowserMcpServer is not available. Prompt \"${name}\" will not be registered.`\n );\n return;\n }\n\n // ponytail: read at registration time so an inline `argsSchema` literal cannot re-register\n // every render. Ceiling: a changed schema is picked up only when `name` or `description`\n // changes; add a caller-supplied deps list, as `useWebMCP` has, if that is ever needed.\n const { argsSchema } = configRef.current;\n const resolvedArgsSchema = argsSchema\n ? normalizeInputSchema(argsSchema).inputSchema\n : undefined;\n\n return modelContext.registerPrompt({\n name,\n ...(description !== undefined && { description }),\n ...(resolvedArgsSchema && { argsSchema: resolvedArgsSchema }),\n get: async (args) => configRef.current.get(args),\n });\n }, [name, description, configRef]);\n\n return { isRegistered: useMcpRegistration(register) };\n}\n","import { useCallback } from 'react';\nimport { getBrowserMcpServer } from './model-context.js';\nimport type { WebMCPResourceConfig, WebMCPResourceReturn } from './types.js';\nimport { useCommittedRef } from './useCommittedRef.js';\nimport { useMcpRegistration } from './useMcpRegistration.js';\n\n/**\n * React hook for registering Model Context Protocol (MCP) resources.\n *\n * This hook handles the complete lifecycle of an MCP resource:\n * - Registers the resource with the installed MCP-B `BrowserMcpServer`\n * - Supports both static URIs and URI templates with parameters\n * - Automatically unregisters on component unmount\n *\n * @param config - Configuration object for the resource\n * @returns Object indicating registration status\n *\n * @public\n *\n * @example\n * Static resource:\n * ```tsx\n * function AppSettingsResource() {\n * const { isRegistered } = useWebMCPResource({\n * uri: 'config://app-settings',\n * name: 'App Settings',\n * description: 'Application configuration',\n * mimeType: 'application/json',\n * read: async (uri) => ({\n * contents: [{\n * uri: uri.href,\n * text: JSON.stringify({ theme: 'dark', language: 'en' })\n * }]\n * }),\n * });\n *\n * return <div>Settings resource {isRegistered ? 'ready' : 'loading'}</div>;\n * }\n * ```\n *\n * @example\n * Dynamic resource with URI template:\n * ```tsx\n * function UserProfileResource() {\n * const { isRegistered } = useWebMCPResource({\n * uri: 'user://{userId}/profile',\n * name: 'User Profile',\n * description: 'User profile data by ID',\n * mimeType: 'application/json',\n * read: async (uri, params) => {\n * const userId = typeof params?.userId === 'string' ? params.userId : '';\n * const profile = await fetchUserProfile(userId);\n * return {\n * contents: [{\n * uri: uri.href,\n * text: JSON.stringify(profile)\n * }]\n * };\n * },\n * });\n *\n * return <div>User profile resource {isRegistered ? 'ready' : 'loading'}</div>;\n * }\n * ```\n */\nexport function useWebMCPResource(config: WebMCPResourceConfig): WebMCPResourceReturn {\n const { uri, name, description, mimeType, read } = config;\n\n const readRef = useCommittedRef(read);\n\n const register = useCallback(() => {\n const modelContext = getBrowserMcpServer();\n if (!modelContext) {\n console.warn(\n `[ReactWebMCP] BrowserMcpServer is not available. Resource \"${uri}\" will not be registered.`\n );\n return;\n }\n\n return modelContext.registerResource({\n uri,\n name,\n ...(description !== undefined && { description }),\n ...(mimeType !== undefined && { mimeType }),\n read: (resolvedUri, params) => readRef.current(resolvedUri, params),\n });\n }, [uri, name, description, mimeType, readRef]);\n\n return { isRegistered: useMcpRegistration(register) };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["useWebMCP"],"sources":["../src/useCommittedRef.ts","../src/client/McpClientProvider.tsx","../src/useWebMCPContext.ts","../src/model-context.ts","../src/useMcpRegistration.ts","../src/useWebMCPPrompt.ts","../src/useWebMCPResource.ts"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef } from 'react';\n\nconst useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;\n\nexport function useCommittedRef<T>(value: T) {\n const ref = useRef(value);\n\n // External MCP work may read values after commit but before passive effects. SSR cannot.\n useIsomorphicLayoutEffect(() => {\n ref.current = value;\n }, [value]);\n\n return ref;\n}\n","import {\n type Client,\n type ConnectOptions,\n type Resource,\n type ServerCapabilities,\n type SubscriptionFilter,\n type Tool as McpTool,\n type Transport,\n} from '@modelcontextprotocol/client';\nimport {\n createContext,\n type ReactElement,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { useCommittedRef } from '../useCommittedRef.js';\n\n/**\n * Context value provided by McpClientProvider.\n *\n * @internal\n */\ninterface McpClientContextValue {\n client: Client;\n tools: McpTool[];\n resources: Resource[];\n isConnected: boolean;\n isLoading: boolean;\n error: Error | null;\n capabilities: ServerCapabilities | null;\n reconnect: (freshTransport?: Transport) => Promise<void>;\n}\n\ntype ConnectionState = 'disconnected' | 'connecting' | 'initializing' | 'connected';\n\nconst McpClientContext = createContext<McpClientContextValue | null>(null);\n\nfunction startListChangedSubscription(\n client: Client,\n filter: SubscriptionFilter,\n refreshLists: () => Promise<void>\n): () => void {\n const controller = new AbortController();\n\n void client\n .listen(filter, { signal: controller.signal })\n .then(async (subscription) => {\n if (controller.signal.aborted) {\n await subscription.close();\n return;\n }\n\n // Modern servers deliver list_changed only after listen is acknowledged.\n await refreshLists();\n })\n .catch((error) => {\n if (!controller.signal.aborted) {\n console.error(\n '[ReactWebMCP:McpClientProvider]',\n 'Failed to listen for list_changed notifications:',\n error\n );\n }\n });\n\n return () => controller.abort();\n}\n\n/**\n * Props for the McpClientProvider component.\n *\n * @public\n */\nexport interface McpClientProviderProps {\n /**\n * React children to render within the provider.\n */\n children: ReactNode;\n\n /**\n * MCP Client instance to use for communication.\n */\n client: Client;\n\n /**\n * Transport instance for the client to connect through.\n */\n transport: Transport;\n\n /**\n * Optional connection options.\n */\n opts?: ConnectOptions;\n}\n\n/**\n * Provider component that manages an MCP client connection and exposes\n * tools, resources, and connection state to child components.\n *\n * This provider handles:\n * - Establishing and maintaining the MCP client connection\n * - Fetching available tools and resources from the server\n * - Listening for server notifications about tool/resource changes\n * - Managing connection state and errors\n * - Automatic cleanup on unmount\n *\n * @param props - Component props\n * @returns Provider component wrapping children\n *\n * @public\n *\n * @example\n * Connect to an MCP server via tab transport:\n * ```tsx\n * import { TabClientTransport } from '@mcp-b/transports';\n * import { McpClientProvider } from '@mcp-b/react-webmcp';\n * import { Client } from '@modelcontextprotocol/client';\n *\n * const client = new Client(\n * { name: 'my-app', version: '1.0.0' },\n * { versionNegotiation: { mode: 'auto' } }\n * );\n *\n * const transport = new TabClientTransport({\n * channelId: 'mcp',\n * targetOrigin: window.location.origin,\n * });\n *\n * function App() {\n * return (\n * <McpClientProvider client={client} transport={transport}>\n * <MyAppContent />\n * </McpClientProvider>\n * );\n * }\n * ```\n *\n * @example\n * Access tools from child components:\n * ```tsx\n * function MyAppContent() {\n * const { tools, isConnected, isLoading } = useMcpClient();\n *\n * if (isLoading) {\n * return <div>Connecting to MCP server...</div>;\n * }\n *\n * if (!isConnected) {\n * return <div>Failed to connect to MCP server</div>;\n * }\n *\n * return (\n * <div>\n * <h2>Available Tools:</h2>\n * <ul>\n * {tools.map(tool => (\n * <li key={tool.name}>{tool.description}</li>\n * ))}\n * </ul>\n * </div>\n * );\n * }\n * ```\n */\nexport function McpClientProvider({\n children,\n client,\n transport,\n opts,\n}: McpClientProviderProps): ReactElement {\n const [resources, setResources] = useState<Resource[]>([]);\n const [tools, setTools] = useState<McpTool[]>([]);\n const [error, setError] = useState<Error | null>(null);\n const [connectionState, setConnectionState] = useState<ConnectionState>('disconnected');\n const [capabilities, setCapabilities] = useState<ServerCapabilities | null>(null);\n const isConnected = connectionState === 'initializing' || connectionState === 'connected';\n const isLoading = connectionState === 'connecting' || connectionState === 'initializing';\n const connectionStateRef = useRef<ConnectionState>('disconnected');\n const connectionGenerationRef = useRef(0);\n const closePromiseRef = useRef<Promise<void> | null>(null);\n const providerCloseRef = useRef<object | null>(null);\n const inventoryRequestRef = useRef(0);\n const requestOptsRef = useCommittedRef(opts);\n\n /**\n * Refreshes every provider-owned inventory list and clears a prior inventory\n * error only after the complete refresh succeeds.\n */\n const refreshInventory = useCallback(\n async (connectionGeneration: number): Promise<void> => {\n const inventoryRequest = ++inventoryRequestRef.current;\n const serverCapabilities = client.getServerCapabilities();\n\n try {\n const [resourceResponse, toolResponse] = await Promise.all([\n serverCapabilities?.resources\n ? client.listResources(undefined, { cacheMode: 'refresh' })\n : undefined,\n serverCapabilities?.tools\n ? client.listTools(undefined, { cacheMode: 'refresh' })\n : undefined,\n ]);\n\n if (\n connectionGeneration !== connectionGenerationRef.current ||\n inventoryRequest !== inventoryRequestRef.current\n ) {\n return;\n }\n\n setResources(resourceResponse?.resources ?? []);\n setTools(toolResponse?.tools ?? []);\n setError(null);\n } catch (cause) {\n const error = cause instanceof Error ? cause : new Error(String(cause));\n if (\n connectionGeneration !== connectionGenerationRef.current ||\n inventoryRequest !== inventoryRequestRef.current\n ) {\n return;\n }\n setError(error);\n throw error;\n }\n },\n [client]\n );\n\n /**\n * Connects a disconnected client, or retries inventory discovery when the\n * MCP handshake is already alive. Calls made while work is in progress no-op.\n *\n * Pass a fresh transport after a one-shot transport has closed.\n */\n const reconnect = useCallback(\n async (freshTransport?: Transport): Promise<void> => {\n if (connectionStateRef.current === 'connected' && freshTransport === undefined) {\n const connectionGeneration = connectionGenerationRef.current;\n connectionStateRef.current = 'initializing';\n setConnectionState('initializing');\n\n try {\n await refreshInventory(connectionGeneration);\n } catch {\n // Inventory failure does not undo the completed MCP handshake.\n }\n\n if (connectionGeneration === connectionGenerationRef.current) {\n connectionStateRef.current = 'connected';\n setConnectionState('connected');\n }\n return;\n }\n\n if (connectionStateRef.current !== 'disconnected') {\n return;\n }\n\n connectionStateRef.current = 'connecting';\n setConnectionState('connecting');\n setError(null);\n const connectionGeneration = connectionGenerationRef.current;\n\n try {\n await closePromiseRef.current;\n if (connectionGeneration !== connectionGenerationRef.current) {\n return;\n }\n await client.connect(freshTransport ?? transport, requestOptsRef.current);\n if (connectionGeneration !== connectionGenerationRef.current) {\n return;\n }\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n if (connectionGeneration === connectionGenerationRef.current) {\n connectionStateRef.current = 'disconnected';\n setConnectionState('disconnected');\n setError(err);\n }\n throw err;\n }\n\n const caps = client.getServerCapabilities();\n setCapabilities(caps ?? null);\n connectionStateRef.current = 'initializing';\n setConnectionState('initializing');\n\n try {\n await refreshInventory(connectionGeneration);\n } catch {\n // Inventory failure does not undo the completed MCP handshake.\n }\n\n if (connectionGeneration === connectionGenerationRef.current) {\n connectionStateRef.current = 'connected';\n setConnectionState('connected');\n }\n },\n [client, transport, refreshInventory, requestOptsRef]\n );\n\n useEffect(() => {\n if (!isConnected) {\n return;\n }\n\n const serverCapabilities = client.getServerCapabilities();\n\n const resourcesListChanged = serverCapabilities?.resources?.listChanged === true;\n const toolsListChanged = serverCapabilities?.tools?.listChanged === true;\n const refreshLists = async () => {\n try {\n await refreshInventory(connectionGenerationRef.current);\n } catch (error) {\n console.error(\n '[ReactWebMCP:McpClientProvider]',\n 'Failed to refresh tools/resources after list_changed:',\n error\n );\n }\n };\n\n if (resourcesListChanged) {\n client.setNotificationHandler('notifications/resources/list_changed', refreshLists);\n }\n\n if (toolsListChanged) {\n client.setNotificationHandler('notifications/tools/list_changed', refreshLists);\n }\n\n const hasListChanged = resourcesListChanged || toolsListChanged;\n const stopListening =\n client.getProtocolEra() === 'modern' && hasListChanged\n ? startListChangedSubscription(\n client,\n {\n ...(toolsListChanged && { toolsListChanged: true }),\n ...(resourcesListChanged && { resourcesListChanged: true }),\n },\n refreshLists\n )\n : undefined;\n if (!stopListening && hasListChanged) {\n // Legacy servers deliver list_changed unsolicited once handlers are installed.\n void refreshLists();\n }\n\n return () => {\n if (resourcesListChanged) {\n client.removeNotificationHandler('notifications/resources/list_changed');\n }\n\n if (toolsListChanged) {\n client.removeNotificationHandler('notifications/tools/list_changed');\n }\n\n stopListening?.();\n };\n }, [client, isConnected, refreshInventory]);\n\n useEffect(() => {\n let active = true;\n const connectionGeneration = connectionGenerationRef.current;\n const previousOnclose = client.onclose;\n const handleClientClose = () => {\n try {\n previousOnclose?.();\n } finally {\n if (active && providerCloseRef.current === null) {\n connectionGenerationRef.current += 1;\n inventoryRequestRef.current += 1;\n connectionStateRef.current = 'disconnected';\n setConnectionState('disconnected');\n setCapabilities(null);\n setResources([]);\n setTools([]);\n }\n }\n };\n client.onclose = handleClientClose;\n connectionStateRef.current = 'disconnected';\n setConnectionState('disconnected');\n setCapabilities(null);\n setResources([]);\n setTools([]);\n\n // Initial connection - reconnect() has its own guard to prevent concurrent connections\n reconnect().catch((err) => {\n if (connectionGeneration === connectionGenerationRef.current) {\n console.error('[ReactWebMCP:McpClientProvider]', 'Failed to connect MCP client:', err);\n }\n });\n\n return () => {\n active = false;\n if (client.onclose === handleClientClose) {\n if (previousOnclose) {\n client.onclose = previousOnclose;\n } else {\n Reflect.deleteProperty(client, 'onclose');\n }\n }\n connectionGenerationRef.current += 1;\n inventoryRequestRef.current += 1;\n connectionStateRef.current = 'disconnected';\n const closeToken = {};\n providerCloseRef.current = closeToken;\n closePromiseRef.current = client\n .close()\n .catch((error: unknown) => {\n console.error('[ReactWebMCP:McpClientProvider]', 'Failed to close MCP client:', error);\n })\n .finally(() => {\n if (providerCloseRef.current === closeToken) {\n providerCloseRef.current = null;\n }\n });\n };\n }, [client, transport, reconnect]);\n\n const contextValue = useMemo(\n () => ({\n client,\n tools,\n resources,\n isConnected,\n isLoading,\n error,\n capabilities,\n reconnect,\n }),\n [client, tools, resources, isConnected, isLoading, error, capabilities, reconnect]\n );\n\n return <McpClientContext.Provider value={contextValue}>{children}</McpClientContext.Provider>;\n}\n\n/**\n * Hook to access the MCP client context.\n * Must be used within an {@link McpClientProvider}.\n *\n * @returns The MCP client context including client instance, tools, resources, and connection state\n * @throws Error if used outside of McpClientProvider\n *\n * @public\n *\n * @example\n * ```tsx\n * function ToolsList() {\n * const { tools, isConnected, error, reconnect } = useMcpClient();\n *\n * if (error) {\n * return (\n * <div>\n * Error: {error.message}\n * <button onClick={() => void reconnect()}>Retry</button>\n * </div>\n * );\n * }\n *\n * if (!isConnected) {\n * return <div>Not connected</div>;\n * }\n *\n * return (\n * <ul>\n * {tools.map(tool => (\n * <li key={tool.name}>{tool.description}</li>\n * ))}\n * </ul>\n * );\n * }\n * ```\n */\nexport function useMcpClient() {\n const context = useContext(McpClientContext);\n if (!context) {\n throw new Error('useMcpClient must be used within an McpClientProvider');\n }\n return context;\n}\n","import { useWebMCP, type WebMCPConfig, type WebMCPReturn } from 'usewebmcp';\n\n/**\n * Convenience hook for exposing read-only context data to AI assistants.\n *\n * This is a simplified wrapper around {@link useWebMCP} specifically designed for\n * context tools that expose data without performing actions. The hook automatically\n * configures appropriate annotations (read-only, idempotent) and handles value\n * serialization.\n *\n * JSON-compatible values are normalized to text plus `structuredContent`.\n *\n * @template T - The type of context data to expose\n *\n * @param name - Unique identifier for the context tool (e.g., 'context_current_post')\n * @param description - Human-readable description of the context for AI assistants\n * @param getValue - Function that returns the current context value\n * @param options - Registration options; enabled defaults to true\n * @returns Tool execution state and control methods\n *\n * @public\n *\n * @example\n * Expose current post context:\n * ```tsx\n * function PostDetailPage() {\n * const { postId } = useParams();\n * const { data: post } = useQuery(['post', postId], () => fetchPost(postId));\n *\n * useWebMCPContext(\n * 'context_current_post',\n * 'Get the currently viewed post ID and metadata',\n * () => ({\n * postId,\n * title: post?.title,\n * author: post?.author,\n * tags: post?.tags,\n * createdAt: post?.createdAt,\n * })\n * );\n *\n * return <PostContent post={post} />;\n * }\n * ```\n *\n * @example\n * Expose user session context:\n * ```tsx\n * function AppRoot() {\n * const { user, isAuthenticated } = useAuth();\n *\n * useWebMCPContext(\n * 'context_user_session',\n * 'Get the current user session information',\n * () => ({\n * isAuthenticated,\n * userId: user?.id,\n * email: user?.email,\n * permissions: user?.permissions,\n * })\n * );\n *\n * return <App />;\n * }\n * ```\n */\nexport function useWebMCPContext<T>(\n name: string,\n description: string,\n getValue: () => T,\n options: Pick<WebMCPConfig, 'enabled'> = {}\n): WebMCPReturn {\n return useWebMCP({\n ...options,\n name,\n description,\n annotations: {\n title: `Context: ${name}`,\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: false,\n },\n execute: getValue,\n });\n}\n","import { isBrowserMcpServer, type BrowserMcpServer } from '@mcp-b/webmcp-ts-sdk';\n\nexport function getBrowserMcpServer(): BrowserMcpServer | undefined {\n if (typeof document === 'undefined' || typeof navigator === 'undefined') {\n return undefined;\n }\n\n const modelContext = document.modelContext ?? navigator.modelContext;\n return modelContext && isBrowserMcpServer(modelContext) ? modelContext : undefined;\n}\n","import type { RegistrationHandle } from '@mcp-b/webmcp-types';\nimport { useEffect, useState } from 'react';\n\nexport function useMcpRegistration(\n register: () => RegistrationHandle | undefined,\n enabled = true\n): boolean {\n const [isRegistered, setIsRegistered] = useState(false);\n\n useEffect(() => {\n const registration = enabled ? register() : undefined;\n if (!registration) {\n setIsRegistered(false);\n return;\n }\n\n setIsRegistered(true);\n return () => {\n registration.unregister();\n };\n }, [register, enabled]);\n\n return isRegistered;\n}\n","import { normalizeInputSchema } from '@mcp-b/webmcp-polyfill/schema';\nimport { useCallback } from 'react';\nimport type { WebMCPPromptConfig, WebMCPPromptReturn } from './types.js';\nimport { getBrowserMcpServer } from './model-context.js';\nimport { useCommittedRef } from './useCommittedRef.js';\nimport { useMcpRegistration } from './useMcpRegistration.js';\n\n/**\n * React hook for registering Model Context Protocol (MCP) prompts.\n *\n * This hook handles the complete lifecycle of an MCP prompt:\n * - Registers the prompt with the installed MCP-B `BrowserMcpServer`\n * - Automatically unregisters on component unmount\n *\n * @param config - Configuration object for the prompt\n * @returns Object indicating registration status\n *\n * @public\n *\n * @example\n * Simple prompt without arguments:\n * ```tsx\n * function HelpPrompt() {\n * const { isRegistered } = useWebMCPPrompt({\n * name: 'help',\n * description: 'Get help with using the application',\n * get: async () => ({\n * messages: [{\n * role: 'user',\n * content: { type: 'text', text: 'How do I use this application?' }\n * }]\n * }),\n * });\n *\n * return <div>Help prompt {isRegistered ? 'ready' : 'loading'}</div>;\n * }\n * ```\n *\n * @example\n * Prompt with typed arguments:\n * ```tsx\n * function CodeReviewPrompt() {\n * const { isRegistered } = useWebMCPPrompt({\n * name: 'review_code',\n * description: 'Review code for best practices',\n * argsSchema: {\n * type: 'object',\n * properties: {\n * code: { type: 'string', description: 'The code to review' },\n * language: { type: 'string', description: 'Programming language' },\n * },\n * required: ['code'],\n * } as const,\n * get: async ({ code, language }) => ({\n * messages: [{\n * role: 'user',\n * content: {\n * type: 'text',\n * text: `Please review this ${language ?? ''} code:\\n\\n${code}`\n * }\n * }]\n * }),\n * });\n *\n * return <div>Code review prompt {isRegistered ? 'ready' : 'loading'}</div>;\n * }\n * ```\n */\nexport function useWebMCPPrompt(config: WebMCPPromptConfig): WebMCPPromptReturn {\n const { name, description } = config;\n\n const configRef = useCommittedRef(config);\n\n const register = useCallback(() => {\n const modelContext = getBrowserMcpServer();\n if (!modelContext) {\n console.warn(\n `[ReactWebMCP] BrowserMcpServer is not available. Prompt \"${name}\" will not be registered.`\n );\n return;\n }\n\n // ponytail: read at registration time so an inline `argsSchema` literal cannot re-register\n // every render. Ceiling: a changed schema is picked up only on re-registration;\n // add a caller-supplied deps list, as `useWebMCP` has, if that is ever needed.\n const { argsSchema } = configRef.current;\n const resolvedArgsSchema = argsSchema\n ? normalizeInputSchema(argsSchema).inputSchema\n : undefined;\n\n return modelContext.registerPrompt({\n name,\n ...(description !== undefined && { description }),\n ...(resolvedArgsSchema && { argsSchema: resolvedArgsSchema }),\n get: async (args) => configRef.current.get(args),\n });\n }, [name, description, configRef]);\n\n return { isRegistered: useMcpRegistration(register, config.enabled) };\n}\n","import { useCallback } from 'react';\nimport { getBrowserMcpServer } from './model-context.js';\nimport type { WebMCPResourceConfig, WebMCPResourceReturn } from './types.js';\nimport { useCommittedRef } from './useCommittedRef.js';\nimport { useMcpRegistration } from './useMcpRegistration.js';\n\n/**\n * React hook for registering Model Context Protocol (MCP) resources.\n *\n * This hook handles the complete lifecycle of an MCP resource:\n * - Registers the resource with the installed MCP-B `BrowserMcpServer`\n * - Supports both static URIs and URI templates with parameters\n * - Automatically unregisters on component unmount\n *\n * @param config - Configuration object for the resource\n * @returns Object indicating registration status\n *\n * @public\n *\n * @example\n * Static resource:\n * ```tsx\n * function AppSettingsResource() {\n * const { isRegistered } = useWebMCPResource({\n * uri: 'config://app-settings',\n * name: 'App Settings',\n * description: 'Application configuration',\n * mimeType: 'application/json',\n * read: async (uri) => ({\n * contents: [{\n * uri: uri.href,\n * text: JSON.stringify({ theme: 'dark', language: 'en' })\n * }]\n * }),\n * });\n *\n * return <div>Settings resource {isRegistered ? 'ready' : 'loading'}</div>;\n * }\n * ```\n *\n * @example\n * Dynamic resource with URI template:\n * ```tsx\n * function UserProfileResource() {\n * const { isRegistered } = useWebMCPResource({\n * uri: 'user://{userId}/profile',\n * name: 'User Profile',\n * description: 'User profile data by ID',\n * mimeType: 'application/json',\n * read: async (uri, params) => {\n * const userId = typeof params?.userId === 'string' ? params.userId : '';\n * const profile = await fetchUserProfile(userId);\n * return {\n * contents: [{\n * uri: uri.href,\n * text: JSON.stringify(profile)\n * }]\n * };\n * },\n * });\n *\n * return <div>User profile resource {isRegistered ? 'ready' : 'loading'}</div>;\n * }\n * ```\n */\nexport function useWebMCPResource(config: WebMCPResourceConfig): WebMCPResourceReturn {\n const { uri, name, description, mimeType, read } = config;\n\n const readRef = useCommittedRef(read);\n\n const register = useCallback(() => {\n const modelContext = getBrowserMcpServer();\n if (!modelContext) {\n console.warn(\n `[ReactWebMCP] BrowserMcpServer is not available. Resource \"${uri}\" will not be registered.`\n );\n return;\n }\n\n return modelContext.registerResource({\n uri,\n name,\n ...(description !== undefined && { description }),\n ...(mimeType !== undefined && { mimeType }),\n read: (resolvedUri, params) => readRef.current(resolvedUri, params),\n });\n }, [uri, name, description, mimeType, readRef]);\n\n return { isRegistered: useMcpRegistration(register, config.enabled) };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-b/react-webmcp",
3
- "version": "5.0.1",
3
+ "version": "5.1.0",
4
4
  "description": "React hooks for Model Context Protocol (MCP) - expose React components as AI tools for Claude, ChatGPT, Cursor, and Copilot",
5
5
  "keywords": [
6
6
  "ai",
@@ -61,24 +61,25 @@
61
61
  },
62
62
  "dependencies": {
63
63
  "@modelcontextprotocol/client": "2.0.0",
64
- "@mcp-b/webmcp-ts-sdk": "5.0.1",
65
- "@mcp-b/webmcp-polyfill": "5.0.1",
66
- "@mcp-b/webmcp-types": "5.0.1",
67
- "usewebmcp": "5.0.1"
64
+ "@mcp-b/webmcp-polyfill": "5.1.0",
65
+ "@mcp-b/webmcp-types": "5.1.0",
66
+ "@mcp-b/webmcp-ts-sdk": "5.1.0",
67
+ "usewebmcp": "5.1.0"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@modelcontextprotocol/server": "2.0.0",
71
- "@types/node": "22.17.2",
72
- "@types/react": "^19.2.9",
73
- "@vitest/coverage-v8": "^4.1.7",
74
- "playwright": "^1.60.0",
75
- "react": "^19.1.0",
76
- "react-dom": "^19.1.0",
71
+ "@types/node": "22.20.1",
72
+ "@types/react": "^19.2.18",
73
+ "@vitest/coverage-v8": "4.1.8",
74
+ "playwright": "^1.62.1",
75
+ "react": "^19.2.8",
76
+ "react-dom": "^19.2.8",
77
77
  "typescript": "^5.8.3",
78
- "vite-plus": "^0.1.22",
79
- "vitest": "npm:@voidzero-dev/vite-plus-test@latest",
80
- "vitest-browser-react": "^2.0.4",
81
- "@mcp-b/global": "5.0.1"
78
+ "vite-plus": "0.1.24",
79
+ "vitest": "npm:@voidzero-dev/vite-plus-test@0.1.24",
80
+ "vitest-browser-react": "^2.2.0",
81
+ "@mcp-b/global": "5.1.0",
82
+ "@mcp-b/transports": "5.1.0"
82
83
  },
83
84
  "peerDependencies": {
84
85
  "react": "^18.0.0 || ^19.0.0"
@@ -97,6 +98,6 @@
97
98
  "publish:npm": "pnpm publish --access public",
98
99
  "test": "vp test run",
99
100
  "test:watch": "vp test",
100
- "typecheck": "tsc --noEmit && vp test run --typecheck --silent"
101
+ "typecheck": "tsc --noEmit && vp test run --typecheck.only --silent"
101
102
  }
102
103
  }