@mcp-b/react-webmcp 5.1.0 → 6.0.0-beta.20261001010549

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
@@ -1,192 +1,119 @@
1
1
  # @mcp-b/react-webmcp
2
2
 
3
- > React hooks for Model Context Protocol (MCP) - Let AI agents like Claude, ChatGPT, Cursor, and Copilot control your React components
3
+ React hooks for WebMCP tools with MCP output schemas, prompts, resources, and client connections,
4
+ built on the core [`usewebmcp`](../usewebmcp/README.md) tool hook.
4
5
 
5
- [![npm version](https://img.shields.io/npm/v/@mcp-b/react-webmcp?style=flat-square)](https://www.npmjs.com/package/@mcp-b/react-webmcp)
6
- [![npm downloads](https://img.shields.io/npm/dm/@mcp-b/react-webmcp?style=flat-square)](https://www.npmjs.com/package/@mcp-b/react-webmcp)
7
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
8
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue?style=flat-square)](https://www.typescriptlang.org/)
9
- [![React](https://img.shields.io/badge/React-17--19-61DAFB?style=flat-square&logo=react)](https://react.dev/)
10
-
11
- **[Reference](https://docs.mcp-b.ai/packages/react-webmcp/reference)** | **[React Tutorial](https://docs.mcp-b.ai/tutorials/first-react-tool)** | **[Framework Guides](https://docs.mcp-b.ai/how-to/frameworks)**
12
-
13
- **@mcp-b/react-webmcp** provides React hooks that expose your components as AI-callable tools via the Model Context Protocol. Build AI-powered React applications where Claude, ChatGPT, Gemini, Cursor, and Copilot can interact with your app's functionality.
6
+ ```tsx
7
+ 'use client';
14
8
 
15
- ## Why Use @mcp-b/react-webmcp?
9
+ import '@mcp-b/global';
10
+ import { useWebMCP } from '@mcp-b/react-webmcp';
11
+ import { z } from 'zod';
16
12
 
17
- | Feature | Benefit |
18
- | ---------------------------- | --------------------------------------------------------------------------------- |
19
- | **React-First Design** | Hooks follow React patterns with automatic cleanup and StrictMode support |
20
- | **Type-Safe Schemas** | JSON Schema and Standard JSON Schema input typing, plus JSON Schema output typing |
21
- | **Two-Way Integration** | Both expose tools TO AI agents AND consume tools FROM MCP servers |
22
- | **Execution State Tracking** | Built-in loading, success, and error states for UI feedback |
23
- | **Works with Any AI** | Compatible with Claude, ChatGPT, Gemini, Cursor, Copilot, and any MCP client |
13
+ const calculatorInput = z.object({ left: z.number(), right: z.number() });
24
14
 
25
- ## Installation
15
+ export function CalculatorTool() {
16
+ const tool = useWebMCP({
17
+ name: 'add_numbers',
18
+ description: 'Add two numbers',
19
+ inputSchema: calculatorInput,
20
+ outputSchema: {
21
+ type: 'object',
22
+ properties: { total: { type: 'number' } },
23
+ required: ['total'],
24
+ },
25
+ annotations: { readOnlyHint: true, idempotentHint: true },
26
+ execute: ({ left, right }) => ({ total: left + right }),
27
+ });
26
28
 
27
- ```bash
28
- pnpm add @mcp-b/global @mcp-b/react-webmcp
29
+ return <output>Last total: {tool.state.lastResult?.total ?? 'Not called yet'}</output>;
30
+ }
29
31
  ```
30
32
 
31
- You can omit `@mcp-b/global` when you only consume an MCP server as a client, or when a native
32
- WebMCP implementation supplies `document.modelContext` and you only use the core `useWebMCP` tool
33
- hook. Prompt and resource hooks require the MCP-B extensions installed by
34
- `@mcp-b/global`. If you only want strict core WebMCP hooks, install `usewebmcp` directly.
33
+ For `{ left: 3, right: 4 }`, React state holds `{ total: 7 }`; the agent receives MCP text and `structuredContent`.
34
+
35
+ [API reference](https://docs.mcp-b.ai/packages/react-webmcp/reference) · [Framework setup](https://docs.mcp-b.ai/how-to/frameworks)
35
36
 
36
- For client functionality, you'll also need:
37
+ ## Install
37
38
 
38
39
  ```bash
39
- pnpm add @mcp-b/transports @modelcontextprotocol/client
40
+ pnpm add @mcp-b/react-webmcp @mcp-b/global zod@^4.2
40
41
  ```
41
42
 
42
- **Prerequisites:** Provider hooks require `document.modelContext`. Install `@mcp-b/global`, or use
43
- a native WebMCP implementation for the core `useWebMCP` tool hook.
43
+ Import `@mcp-b/global` once in your client entry. Zod is optional; compatible schema libraries and plain JSON Schema also work.
44
44
 
45
- Provider hooks register tools with `document.modelContext.registerTool(tool, {
46
- signal })` and abort the controller on unmount. The hooks retain a
47
- `navigator.modelContext` fallback for older preview runtimes, but
48
- `document.modelContext` is the canonical surface. Install `@mcp-b/global`
49
- when you need a portable runtime with spec-aligned cleanup behavior.
45
+ | What you use | Runtime needed |
46
+ | ---------------------------------------- | ------------------------------------------------------------------ |
47
+ | `useWebMCP` or `useWebMCPContext` | Native WebMCP, an initialized polyfill, or `@mcp-b/global` |
48
+ | `useWebMCPPrompt` or `useWebMCPResource` | `@mcp-b/global` or a configured `BrowserMcpServer` |
49
+ | `McpClientProvider` and `useMcpClient` | Your MCP client and transport; no `document.modelContext` required |
50
50
 
51
- `outputSchema` is MCP-B helper metadata for output typing and structured MCP
52
- responses. Native Chrome WebMCP does not currently define or enforce it.
51
+ Native WebMCP and the standalone polyfill do not advertise MCP `outputSchema` metadata. Use the MCP-B runtime to expose that metadata to MCP clients.
53
52
 
54
- ## Quick Start - Provider (Registering Tools)
53
+ ## Schemas and results
55
54
 
56
- ```tsx
57
- import '@mcp-b/global';
58
- import { useWebMCP } from '@mcp-b/react-webmcp';
55
+ - The hook calls your schema's converter once and runs its validator before every handler call (local, `executeTool`, and MCP), including async transforms. It ships no validator.
56
+ - Plain JSON Schema supplies metadata and inference only. Reuse immutable schemas to cache conversion and serialization.
57
+ - `outputSchema` types the result. The MCP server validates it on MCP calls; local and native calls bypass that validation. When that check fails, the MCP client receives an error while `state` records the success.
58
+ - Local execution and React state retain your value. Agent calls receive MCP formatting; `formatOutput` can override it.
59
+ - `formatError` defaults to an MCP response with `isError: true` whose text is `error.message`. Async formatters are awaited; local failures and cancellation always reject. A handler that returns `undefined` yields the text `undefined`.
59
60
 
60
- function PostsPage() {
61
- const likeTool = useWebMCP({
62
- name: 'posts_like',
63
- description: 'Like a post by ID. Increments the like count.',
64
- inputSchema: {
65
- type: 'object',
66
- properties: {
67
- postId: { type: 'string', description: 'The post ID to like' },
68
- },
69
- required: ['postId'],
70
- } as const,
71
- outputSchema: {
72
- type: 'object',
73
- properties: {
74
- success: { type: 'boolean' },
75
- postId: { type: 'string' },
76
- },
77
- required: ['success', 'postId'],
78
- } as const,
79
- annotations: {
80
- title: 'Like Post',
81
- readOnlyHint: false,
82
- idempotentHint: true,
83
- },
84
- execute: async (input) => {
85
- await api.posts.like(input.postId);
86
- return { success: true, postId: input.postId };
87
- },
88
- });
61
+ [Input example](#mcp-breact-webmcp) ·
62
+ [Schema guide](https://docs.mcp-b.ai/how-to/use-schemas-and-structured-output) ·
63
+ [Output reference](https://docs.mcp-b.ai/packages/react-webmcp/reference#schema-compatibility)
89
64
 
90
- return (
91
- <div>
92
- {likeTool.state.isExecuting && <Spinner />}
93
- {likeTool.state.error && <ErrorAlert error={likeTool.state.error} />}
94
- </div>
95
- );
96
- }
97
- ```
65
+ ## Expose context, prompts, and resources
98
66
 
99
- ## Quick Start - Client (Consuming Tools)
67
+ ```ts
68
+ 'use client';
100
69
 
101
- ```tsx
102
- import { McpClientProvider, useMcpClient } from '@mcp-b/react-webmcp';
103
- import { TabClientTransport } from '@mcp-b/transports';
104
- import { Client } from '@modelcontextprotocol/client';
105
-
106
- const client = new Client(
107
- { name: 'MyApp', version: '1.0.0' },
108
- { versionNegotiation: { mode: 'auto' } }
109
- );
110
- const transport = new TabClientTransport({
111
- channelId: 'mcp',
112
- targetOrigin: window.location.origin,
113
- });
114
-
115
- function App() {
116
- return (
117
- <McpClientProvider client={client} transport={transport}>
118
- <ToolConsumer />
119
- </McpClientProvider>
120
- );
121
- }
122
-
123
- function ToolConsumer() {
124
- const { client, tools, isConnected } = useMcpClient();
125
-
126
- const handleCallTool = async () => {
127
- const result = await client.callTool({ name: 'posts_like', arguments: { postId: '123' } });
128
- console.log('Result:', result.content[0].text);
129
- };
130
-
131
- return (
132
- <div>
133
- <p>Connected: {isConnected ? 'Yes' : 'No'}</p>
134
- <p>Available Tools: {tools.length}</p>
135
- <button onClick={handleCallTool} disabled={!isConnected}>
136
- Call Tool
137
- </button>
138
- </div>
139
- );
140
- }
141
- ```
142
-
143
- `useMcpClient().reconnect()` retries tool and resource discovery while the client remains connected.
144
- If a one-shot transport closes, construct a new transport and pass it to
145
- `reconnect(newTransport)`; closed transport instances are not generally reusable.
146
-
147
- ## API Overview
70
+ import '@mcp-b/global';
71
+ import { useWebMCPContext, useWebMCPPrompt, useWebMCPResource } from '@mcp-b/react-webmcp';
148
72
 
149
- ### Provider Hooks
73
+ export function PageTools({ title }: { title: string }) {
74
+ useWebMCPContext('page_context', 'Get the current page title', () => ({ title }));
150
75
 
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 |
76
+ useWebMCPPrompt({
77
+ name: 'summarize_page',
78
+ get: () => ({
79
+ messages: [{ role: 'user', content: { type: 'text', text: `Summarize: ${title}` } }],
80
+ }),
81
+ });
157
82
 
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.
83
+ useWebMCPResource({
84
+ name: 'Page title',
85
+ uri: 'page://title',
86
+ read: async (uri) => ({
87
+ contents: [{ uri: uri.href, mimeType: 'text/plain', text: title }],
88
+ }),
89
+ });
162
90
 
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.
91
+ return null;
92
+ }
93
+ ```
166
94
 
167
- ### Client Hooks
95
+ These hooks use the latest committed props and clean up on unmount. Set `enabled: false`
96
+ to unregister. [Prompt and resource guide](https://docs.mcp-b.ai/how-to/register-prompts-and-resources).
168
97
 
169
- | Hook / Component | Description |
170
- | ------------------- | --------------------------------------------------------- |
171
- | `McpClientProvider` | Provider component managing an MCP client connection |
172
- | `useMcpClient()` | Access client, tools, connection status, and capabilities |
98
+ ## Consume an MCP server
173
99
 
174
- ## Schema Compatibility
100
+ Wrap your UI in `McpClientProvider`, supplying stable client and transport instances.
101
+ `useMcpClient()` exposes tools, resources, connection state, and errors.
102
+ [Client setup and example](https://docs.mcp-b.ai/packages/react-webmcp/reference#client-hooks).
175
103
 
176
- Inputs accept JSON Schema or Standard JSON Schema v1 implementations such as Zod 4.2+. Outputs use JSON Schema for typed `structuredContent`.
104
+ ## State and lifecycle
177
105
 
178
- ## Related Packages
106
+ The tool hook returns the core hook's state and controls and shares its `enabled`, cancellation,
107
+ and registration behavior ([lifecycle reference](https://docs.mcp-b.ai/packages/usewebmcp/reference)).
108
+ Prompt and resource hooks return `isRegistered`.
109
+ `'use client'` is preserved; server rendering and StrictMode are tested on React 19, and React 18 is in the peer range.
179
110
 
180
- - [`@mcp-b/global`](https://docs.mcp-b.ai/packages/global/reference) - Full MCP-B browser runtime (required for provider hooks)
181
- - [`@mcp-b/transports`](https://docs.mcp-b.ai/packages/transports/reference) - Browser-specific MCP transports
182
- - [`chrome-devtools-mcp`](https://github.com/ChromeDevTools/chrome-devtools-mcp) - Upstream Chrome DevTools MCP server
183
- - [`usewebmcp`](../usewebmcp) - React hooks for strict core WebMCP API only
111
+ [Declarative form attributes](https://docs.mcp-b.ai/packages/react-webmcp/reference#declarative-form-attributes)
184
112
 
185
- ## Resources
113
+ ## Development
186
114
 
187
- - [WebMCP Documentation](https://docs.mcp-b.ai)
188
- - [Model Context Protocol Spec](https://modelcontextprotocol.io)
115
+ Run `pnpm build` then `pnpm test:hooks` from the repository root. [Harness details](../../docs/TESTING.md#react-hook-harness).
189
116
 
190
117
  ## License
191
118
 
192
- MIT - see [LICENSE](../../LICENSE) for details
119
+ [MIT](../../LICENSE)
package/dist/index.d.ts CHANGED
@@ -1,10 +1,46 @@
1
- import { InferOutput, InferToolInput, ToolExecutionState, WebMCPConfig, WebMCPConfig as WebMCPConfig$1, WebMCPReturn, WebMCPReturn as WebMCPReturn$1, useWebMCP } from "usewebmcp";
2
- import { ReactElement, ReactNode } from "react";
3
- import { ToolInputSchema, ToolInputSchema as ToolInputSchema$1 } from "@mcp-b/webmcp-polyfill/schema";
4
- import { BrowserMcpServer as ModelContextProtocol, PromptDescriptor, PromptDescriptor as PromptDescriptor$1, ResourceDescriptor, ResourceDescriptor as ResourceDescriptor$1 } from "@mcp-b/webmcp-ts-sdk";
1
+ import { DependencyList, ReactElement, ReactNode } from "react";
2
+ import { ToolInputSchema, ToolInputSchema as ToolInputSchema$1 } from "@mcp-b/webmcp-ts-sdk/schema";
3
+ import { InferToolInput as InferToolInput$1, ToolExecutionState, WebMCP, WebMCP as WebMCP$1, WebMCPConfig as WebMCPConfig$1, WebMCPReturn as WebMCPReturn$1 } from "usewebmcp";
4
+ import { BrowserMcpServer as ModelContextProtocol, CallToolResult, InferJsonSchema, InputSchema, JsonSchemaForInference, MaybePromise, PromptDescriptor, PromptDescriptor as PromptDescriptor$1, ResourceDescriptor, ResourceDescriptor as ResourceDescriptor$1, ToolAnnotations, ToolAnnotations as ToolAnnotations$1, ToolDescriptor } from "@mcp-b/webmcp-ts-sdk";
5
+ import { StandardJSONSchemaV1, StandardSchemaV1 } from "@standard-schema/spec";
5
6
  import { Client, ConnectOptions, Resource, ServerCapabilities, Tool, Transport } from "@modelcontextprotocol/client";
6
- import { CallToolResult, MaybePromise, ToolAnnotations, ToolDescriptor } from "@mcp-b/webmcp-types";
7
7
 
8
+ //#region src/types.d.ts
9
+ /** Infers MCP-B structured output from its JSON Schema. */
10
+ type InferOutput<T extends JsonSchemaForInference | undefined = undefined> = [T] extends [undefined] ? unknown : T extends JsonSchemaForInference ? InferJsonSchema<T> : unknown;
11
+ type InferToolInput<T extends ToolInputSchema$1> = T extends StandardJSONSchemaV1 ? StandardJSONSchemaV1.InferInput<T> : InferToolInput$1<Exclude<T, StandardJSONSchemaV1>>;
12
+ type InferValidatedToolInput<T extends ToolInputSchema$1> = T extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<T> : InferToolInput<T>;
13
+ /** Core hook configuration with opt-in MCP metadata. */
14
+ interface WebMCPConfig<TInput extends ToolInputSchema$1 = InputSchema, TOutput extends JsonSchemaForInference | undefined = undefined> extends Omit<WebMCPConfig$1<InputSchema, InferOutput<TOutput>>, 'annotations' | 'execute' | 'inputSchema'> {
15
+ inputSchema?: TInput;
16
+ execute: ToolExecuteFunction<TInput, TOutput>;
17
+ outputSchema?: TOutput;
18
+ annotations?: ToolAnnotations$1;
19
+ formatOutput?: (result: InferOutput<TOutput>) => MaybePromise<unknown>;
20
+ formatError?: (error: Error) => MaybePromise<unknown>;
21
+ }
22
+ type ToolExecuteFunction<TInput extends ToolInputSchema$1 = InputSchema, TOutput extends JsonSchemaForInference | undefined = undefined> = (input: InferValidatedToolInput<TInput>, options: WebMCP$1.ToolExecuteCallbackOptions) => MaybePromise<InferOutput<TOutput>>;
23
+ type WebMCPReturn<TOutput extends JsonSchemaForInference | undefined = undefined, TInput extends ToolInputSchema$1 = InputSchema> = Omit<WebMCPReturn$1<InputSchema, InferOutput<TOutput>>, 'execute'> & {
24
+ execute: (input: InferToolInput<TInput>, options?: WebMCP$1.ToolExecuteCallbackOptions) => Promise<InferOutput<TOutput>>;
25
+ };
26
+ /** A single message returned by {@link WebMCPPromptConfig.get}. */
27
+ type PromptMessage = Awaited<ReturnType<PromptDescriptor$1['get']>>['messages'][number];
28
+ /** A single entry returned by {@link WebMCPResourceConfig.read}. */
29
+ type ResourceContents = Awaited<ReturnType<ResourceDescriptor$1['read']>>['contents'][number];
30
+ type WebMCPPromptConfig = Pick<PromptDescriptor$1, 'name' | 'description'> & Pick<WebMCPConfig, 'enabled'> & {
31
+ argsSchema?: ToolInputSchema$1;
32
+ get: (args: Parameters<PromptDescriptor$1['get']>[0]) => MaybePromise<Awaited<ReturnType<PromptDescriptor$1['get']>>>;
33
+ };
34
+ interface WebMCPPromptReturn {
35
+ isRegistered: boolean;
36
+ }
37
+ type WebMCPResourceConfig = ResourceDescriptor$1 & Pick<WebMCPConfig, 'enabled'>;
38
+ type WebMCPResourceReturn = WebMCPPromptReturn;
39
+ //#endregion
40
+ //#region src/useWebMCP.d.ts
41
+ /** The core React lifecycle with MCP result formatting and output metadata. */
42
+ declare function useWebMCP<const TInput extends ToolInputSchema$1 = InputSchema, const TOutput extends JsonSchemaForInference | undefined = undefined>(config: WebMCPConfig<TInput, TOutput>, deps?: DependencyList): WebMCPReturn<TOutput, TInput>;
43
+ //#endregion
8
44
  //#region src/client/McpClientProvider.d.ts
9
45
  /**
10
46
  * Context value provided by McpClientProvider.
@@ -158,21 +194,6 @@ declare function McpClientProvider({
158
194
  */
159
195
  declare function useMcpClient(): McpClientContextValue;
160
196
  //#endregion
161
- //#region src/types.d.ts
162
- /** A single message returned by {@link WebMCPPromptConfig.get}. */
163
- type PromptMessage = Awaited<ReturnType<PromptDescriptor$1['get']>>['messages'][number];
164
- /** A single entry returned by {@link WebMCPResourceConfig.read}. */
165
- type ResourceContents = Awaited<ReturnType<ResourceDescriptor$1['read']>>['contents'][number];
166
- type WebMCPPromptConfig = Pick<PromptDescriptor$1, 'name' | 'description'> & Pick<WebMCPConfig$1, 'enabled'> & {
167
- argsSchema?: ToolInputSchema$1;
168
- get: (args: Parameters<PromptDescriptor$1['get']>[0]) => MaybePromise<Awaited<ReturnType<PromptDescriptor$1['get']>>>;
169
- };
170
- interface WebMCPPromptReturn {
171
- isRegistered: boolean;
172
- }
173
- type WebMCPResourceConfig = ResourceDescriptor$1 & Pick<WebMCPConfig$1, 'enabled'>;
174
- type WebMCPResourceReturn = WebMCPPromptReturn;
175
- //#endregion
176
197
  //#region src/useWebMCPContext.d.ts
177
198
  /**
178
199
  * Convenience hook for exposing read-only context data to AI assistants.
@@ -238,7 +259,7 @@ type WebMCPResourceReturn = WebMCPPromptReturn;
238
259
  * }
239
260
  * ```
240
261
  */
241
- declare function useWebMCPContext<T>(name: string, description: string, getValue: () => T, options?: Pick<WebMCPConfig$1, 'enabled'>): WebMCPReturn$1;
262
+ declare function useWebMCPContext<T>(name: string, description: string, getValue: () => T, options?: Pick<WebMCPConfig, 'enabled'>): WebMCPReturn;
242
263
  //#endregion
243
264
  //#region src/useWebMCPPrompt.d.ts
244
265
  /**
@@ -388,5 +409,5 @@ declare module 'react' {
388
409
  }
389
410
  }
390
411
  //#endregion
391
- export { type CallToolResult, type InferOutput, type InferToolInput, McpClientProvider, type McpClientProviderProps, type ModelContextProtocol, type PromptDescriptor, type PromptMessage, type ResourceContents, type ResourceDescriptor, type ToolAnnotations, type ToolDescriptor, type ToolExecutionState, type ToolInputSchema, type WebMCPConfig, type WebMCPPromptConfig, type WebMCPPromptReturn, type WebMCPResourceConfig, type WebMCPResourceReturn, type WebMCPReturn, useMcpClient, useWebMCP, useWebMCPContext, useWebMCPPrompt, useWebMCPResource };
412
+ export { type CallToolResult, type InferOutput, type InferToolInput, type InferValidatedToolInput, McpClientProvider, type McpClientProviderProps, type ModelContextProtocol, type PromptDescriptor, type PromptMessage, type ResourceContents, type ResourceDescriptor, type ToolAnnotations, type ToolDescriptor, type ToolExecuteFunction, type ToolExecutionState, type ToolInputSchema, type WebMCP, type WebMCPConfig, type WebMCPPromptConfig, type WebMCPPromptReturn, type WebMCPResourceConfig, type WebMCPResourceReturn, type WebMCPReturn, useMcpClient, useWebMCP, useWebMCPContext, useWebMCPPrompt, useWebMCPResource };
392
413
  //# sourceMappingURL=index.d.ts.map
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`),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};
1
+ "use client";import{createContext as e,useCallback as t,useContext as n,useEffect as r,useLayoutEffect as i,useMemo as a,useRef as o,useState as s}from"react";import{isMcpStandardSchema as c,normalizeInputSchema as l,normalizeToolResponse as u}from"@mcp-b/webmcp-ts-sdk/schema";import{useWebMCPWithAdapter as d}from"usewebmcp/internal";import{jsx as f}from"react/jsx-runtime";async function p(e,t){if(!e||!c(e))return t;let n=await e[`~standard`].validate(t);if(`value`in n)return n.value;throw TypeError(`Invalid tool input: ${(n.issues??[]).map(e=>e.message).join(`; `)}`)}function m(e,t){let n=a(()=>{try{return{schema:e.inputSchema&&{...l(e.inputSchema).inputSchema}}}catch(e){return{error:e instanceof Error?e:Error(String(e))}}},[e.inputSchema]);return d({name:e.name,...e.title!==void 0&&{title:e.title},description:e.description,...n.schema!==void 0&&{inputSchema:n.schema},...e.enabled!==void 0&&{enabled:e.enabled},...e.exposedTo!==void 0&&{exposedTo:e.exposedTo},execute:async(t,n)=>{let r=await e.execute(await p(e.inputSchema,t),n);if(e.outputSchema&&u(r).structuredContent===void 0)throw TypeError(`Tool "${e.name}" outputSchema requires execute to return a JSON-serializable result`);return r}},t,{descriptor:{...e.outputSchema!==void 0&&{outputSchema:e.outputSchema},...e.annotations!==void 0&&{annotations:e.annotations}},...n.error&&{preparationError:n.error},formatOutput:e.formatOutput??u,formatError:e.formatError??(e=>({content:[{type:`text`,text:e.message}],isError:!0}))})}const h=globalThis.window===void 0?r:i;function g(e){let t=o(e);return h(()=>{t.current=e},[e]),t}const _=e(null);function v({children:e,client:n,transport:i,opts:c}){let[l,u]=s([]),[d,p]=s([]),[m,h]=s(null),[v,y]=s(`disconnected`),[b,x]=s(null),S=v===`initializing`||v===`connected`,C=v===`connecting`||v===`initializing`,w=o(`disconnected`),T=o(0),E=o(null),D=o(null),O=o(0),k=g(c),A=t(async e=>{let t=++O.current,r=()=>e===T.current&&t===O.current,i=n.getServerCapabilities();try{let[e,t]=await Promise.all([i?.resources?n.listResources(void 0,{cacheMode:`refresh`}):void 0,i?.tools?n.listTools(void 0,{cacheMode:`refresh`}):void 0]);if(!r())return;u(e?.resources??[]),p(t?.tools??[]),h(null)}catch(e){if(!r())return;let t=e instanceof Error?e:Error(String(e));throw h(t),t}},[n]),j=t(async e=>{let t=T.current;if(w.current!==`connected`||e!==void 0){if(w.current!==`disconnected`)return;w.current=`connecting`,y(`connecting`),h(null);try{if(await E.current,t!==T.current||(await n.connect(e??i,k.current),t!==T.current))return}catch(e){let n=e instanceof Error?e:Error(String(e));throw t===T.current&&(w.current=`disconnected`,y(`disconnected`),h(n)),n}x(n.getServerCapabilities()??null)}w.current=`initializing`,y(`initializing`);try{await A(t)}catch{}t===T.current&&(w.current=`connected`,y(`connected`))},[n,i,A,k]);r(()=>{if(!S)return;let e=n.getServerCapabilities(),t=e?.resources?.listChanged===!0,r=e?.tools?.listChanged===!0;if(!t&&!r)return;let i=async()=>{try{await A(T.current)}catch(e){console.error(`[ReactWebMCP:McpClientProvider]`,`Failed to refresh tools/resources after list_changed:`,e)}};t&&n.setNotificationHandler(`notifications/resources/list_changed`,i),r&&n.setNotificationHandler(`notifications/tools/list_changed`,i);let a=new AbortController;return n.getProtocolEra()===`modern`?n.listen({...r&&{toolsListChanged:!0},...t&&{resourcesListChanged:!0}},{signal:a.signal}).then(async e=>{if(a.signal.aborted){await e.close();return}await i()}).catch(e=>{a.signal.aborted||console.error(`[ReactWebMCP:McpClientProvider]`,`Failed to listen for list_changed notifications:`,e)}):i(),()=>{t&&n.removeNotificationHandler(`notifications/resources/list_changed`),r&&n.removeNotificationHandler(`notifications/tools/list_changed`),a.abort()}},[n,S,A]),r(()=>{let e=!0,t=T.current,r=n.onclose,i=()=>{try{r?.()}finally{e&&D.current===null&&(T.current+=1,O.current+=1,w.current=`disconnected`,y(`disconnected`),x(null),u([]),p([]))}};return n.onclose=i,w.current=`disconnected`,y(`disconnected`),x(null),u([]),p([]),j().catch(e=>{t===T.current&&console.error(`[ReactWebMCP:McpClientProvider]`,`Failed to connect MCP client:`,e)}),()=>{e=!1,n.onclose===i&&(r?n.onclose=r:Reflect.deleteProperty(n,`onclose`)),T.current+=1,O.current+=1,w.current=`disconnected`;let t={};D.current=t,E.current=n.close().catch(e=>{console.error(`[ReactWebMCP:McpClientProvider]`,`Failed to close MCP client:`,e)}).finally(()=>{D.current===t&&(D.current=null)})}},[n,i,j]);let M=a(()=>({client:n,tools:d,resources:l,isConnected:S,isLoading:C,error:m,capabilities:b,reconnect:j}),[n,d,l,S,C,m,b,j]);return f(_.Provider,{value:M,children:e})}function y(){let e=n(_);if(!e)throw Error(`useMcpClient must be used within an McpClientProvider`);return e}function b(e,t,n,r={}){return m({...r,name:e,description:t,annotations:{title:`Context: ${e}`,readOnlyHint:!0,idempotentHint:!0,destructiveHint:!1,openWorldHint:!1},execute:n})}function x(e){return`__isBrowserMcpServer`in e&&e.__isBrowserMcpServer===!0}function S(){let e=globalThis.document?.modelContext;return e&&x(e)?e:void 0}function C(e,t=!0){let[n,i]=s(!1);return r(()=>{let n=t?e():void 0;if(!n){i(!1);return}return i(!0),()=>{n.unregister()}},[e,t]),n}function w(e){let{name:n,description:r}=e,i=g(e);return{isRegistered:C(t(()=>{let e=S();if(!e){console.warn(`[ReactWebMCP] BrowserMcpServer is not available. Prompt "${n}" will not be registered.`);return}let{argsSchema:t}=i.current,a=t?l(t).inputSchema:void 0;return e.registerPrompt({name:n,...r!==void 0&&{description:r},...a&&{argsSchema:a},get:async e=>i.current.get(e)})},[n,r,i]),e.enabled)}}function T(e){let{uri:n,name:r,description:i,mimeType:a,read:o}=e,s=g(o);return{isRegistered:C(t(()=>{let e=S();if(!e){console.warn(`[ReactWebMCP] BrowserMcpServer is not available. Resource "${n}" will not be registered.`);return}return e.registerResource({uri:n,name:r,...i!==void 0&&{description:i},...a!==void 0&&{mimeType:a},read:(e,t)=>s.current(e,t)})},[n,r,i,a,s]),e.enabled)}}export{v as McpClientProvider,y as useMcpClient,m as useWebMCP,b as useWebMCPContext,w as useWebMCPPrompt,T 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 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":"2XAEA,MAAM,EAA4B,OAAO,OAAW,IAAc,EAAY,EAE9E,SAAgB,EAAmB,EAAU,CAC3C,IAAM,EAAM,EAAO,CAAK,EAOxB,OAJA,MAAgC,CAC9B,EAAI,QAAU,CAChB,EAAG,CAAC,CAAK,CAAC,EAEH,CACT,CC2BA,MAAM,EAAmB,EAA4C,IAAI,EAEzE,SAAS,EACP,EACA,EACA,EACY,CACZ,IAAM,EAAa,IAAI,gBAuBvB,OArBA,EACG,OAAO,EAAQ,CAAE,OAAQ,EAAW,MAAO,CAAC,EAC5C,KAAK,KAAO,IAAiB,CAC5B,GAAI,EAAW,OAAO,QAAS,CAC7B,MAAM,EAAa,MAAM,EACzB,MACF,CAGA,MAAM,EAAa,CACrB,CAAC,EACA,MAAO,GAAU,CACX,EAAW,OAAO,SACrB,QAAQ,MACN,kCACA,mDACA,CACF,CAEJ,CAAC,MAEU,EAAW,MAAM,CAChC,CAkGA,SAAgB,EAAkB,CAChC,WACA,SACA,YACA,QACuC,CACvC,GAAM,CAAC,EAAW,GAAgB,EAAqB,CAAC,CAAC,EACnD,CAAC,EAAO,GAAY,EAAoB,CAAC,CAAC,EAC1C,CAAC,EAAO,GAAY,EAAuB,IAAI,EAC/C,CAAC,EAAiB,GAAsB,EAA0B,cAAc,EAChF,CAAC,EAAc,GAAmB,EAAoC,IAAI,EAC1E,EAAc,IAAoB,gBAAkB,IAAoB,YACxE,EAAY,IAAoB,cAAgB,IAAoB,eACpE,EAAqB,EAAwB,cAAc,EAC3D,EAA0B,EAAO,CAAC,EAClC,EAAkB,EAA6B,IAAI,EACnD,EAAmB,EAAsB,IAAI,EAC7C,EAAsB,EAAO,CAAC,EAC9B,EAAiB,EAAgB,CAAI,EAMrC,EAAmB,EACvB,KAAO,IAAgD,CACrD,IAAM,EAAmB,EAAE,EAAoB,QACzC,EAAqB,EAAO,sBAAsB,EAExD,GAAI,CACF,GAAM,CAAC,EAAkB,GAAgB,MAAM,QAAQ,IAAI,CACzD,GAAoB,UAChB,EAAO,cAAc,IAAA,GAAW,CAAE,UAAW,SAAU,CAAC,EACxD,IAAA,GACJ,GAAoB,MAChB,EAAO,UAAU,IAAA,GAAW,CAAE,UAAW,SAAU,CAAC,EACpD,IAAA,EACN,CAAC,EAED,GACE,IAAyB,EAAwB,SACjD,IAAqB,EAAoB,QAEzC,OAGF,EAAa,GAAkB,WAAa,CAAC,CAAC,EAC9C,EAAS,GAAc,OAAS,CAAC,CAAC,EAClC,EAAS,IAAI,CACf,OAAS,EAAO,CACd,IAAM,EAAQ,aAAiB,MAAQ,EAAY,MAAM,OAAO,CAAK,CAAC,EACtE,GACE,IAAyB,EAAwB,SACjD,IAAqB,EAAoB,QAEzC,OAGF,MADA,EAAS,CAAK,EACR,CACR,CACF,EACA,CAAC,CAAM,CACT,EAQM,EAAY,EAChB,KAAO,IAA8C,CACnD,GAAI,EAAmB,UAAY,aAAe,IAAmB,IAAA,GAAW,CAC9E,IAAM,EAAuB,EAAwB,QACrD,EAAmB,QAAU,eAC7B,EAAmB,cAAc,EAEjC,GAAI,CACF,MAAM,EAAiB,CAAoB,CAC7C,MAAQ,CAER,CAEI,IAAyB,EAAwB,UACnD,EAAmB,QAAU,YAC7B,EAAmB,WAAW,GAEhC,MACF,CAEA,GAAI,EAAmB,UAAY,eACjC,OAGF,EAAmB,QAAU,aAC7B,EAAmB,YAAY,EAC/B,EAAS,IAAI,EACb,IAAM,EAAuB,EAAwB,QAErD,GAAI,CAMF,GALA,MAAM,EAAgB,QAClB,IAAyB,EAAwB,UAGrD,MAAM,EAAO,QAAQ,GAAkB,EAAW,EAAe,OAAO,EACpE,IAAyB,EAAwB,SACnD,MAEJ,OAAS,EAAG,CACV,IAAM,EAAM,aAAa,MAAQ,EAAQ,MAAM,OAAO,CAAC,CAAC,EAMxD,MALI,IAAyB,EAAwB,UACnD,EAAmB,QAAU,eAC7B,EAAmB,cAAc,EACjC,EAAS,CAAG,GAER,CACR,CAGA,EADa,EAAO,sBACD,GAAK,IAAI,EAC5B,EAAmB,QAAU,eAC7B,EAAmB,cAAc,EAEjC,GAAI,CACF,MAAM,EAAiB,CAAoB,CAC7C,MAAQ,CAER,CAEI,IAAyB,EAAwB,UACnD,EAAmB,QAAU,YAC7B,EAAmB,WAAW,EAElC,EACA,CAAC,EAAQ,EAAW,EAAkB,CAAc,CACtD,EAEA,MAAgB,CACd,GAAI,CAAC,EACH,OAGF,IAAM,EAAqB,EAAO,sBAAsB,EAElD,EAAuB,GAAoB,WAAW,cAAgB,GACtE,EAAmB,GAAoB,OAAO,cAAgB,GAC9D,EAAe,SAAY,CAC/B,GAAI,CACF,MAAM,EAAiB,EAAwB,OAAO,CACxD,OAAS,EAAO,CACd,QAAQ,MACN,kCACA,wDACA,CACF,CACF,CACF,EAEI,GACF,EAAO,uBAAuB,uCAAwC,CAAY,EAGhF,GACF,EAAO,uBAAuB,mCAAoC,CAAY,EAGhF,IAAM,EAAiB,GAAwB,EACzC,EACJ,EAAO,eAAe,IAAM,UAAY,EACpC,EACE,EACA,CACE,GAAI,GAAoB,CAAE,iBAAkB,EAAK,EACjD,GAAI,GAAwB,CAAE,qBAAsB,EAAK,CAC3D,EACA,CACF,EACA,IAAA,GAMN,MALI,CAAC,GAAiB,GAEpB,EAAkB,MAGP,CACP,GACF,EAAO,0BAA0B,sCAAsC,EAGrE,GACF,EAAO,0BAA0B,kCAAkC,EAGrE,IAAgB,CAClB,CACF,EAAG,CAAC,EAAQ,EAAa,CAAgB,CAAC,EAE1C,MAAgB,CACd,IAAI,EAAS,GACP,EAAuB,EAAwB,QAC/C,EAAkB,EAAO,QACzB,MAA0B,CAC9B,GAAI,CACF,IAAkB,CACpB,QAAU,CACJ,GAAU,EAAiB,UAAY,OACzC,EAAwB,SAAW,EACnC,EAAoB,SAAW,EAC/B,EAAmB,QAAU,eAC7B,EAAmB,cAAc,EACjC,EAAgB,IAAI,EACpB,EAAa,CAAC,CAAC,EACf,EAAS,CAAC,CAAC,EAEf,CACF,EAeA,MAdA,GAAO,QAAU,EACjB,EAAmB,QAAU,eAC7B,EAAmB,cAAc,EACjC,EAAgB,IAAI,EACpB,EAAa,CAAC,CAAC,EACf,EAAS,CAAC,CAAC,EAGX,EAAU,EAAE,MAAO,GAAQ,CACrB,IAAyB,EAAwB,SACnD,QAAQ,MAAM,kCAAmC,gCAAiC,CAAG,CAEzF,CAAC,MAEY,CACX,EAAS,GACL,EAAO,UAAY,IACjB,EACF,EAAO,QAAU,EAEjB,QAAQ,eAAe,EAAQ,SAAS,GAG5C,EAAwB,SAAW,EACnC,EAAoB,SAAW,EAC/B,EAAmB,QAAU,eAC7B,IAAM,EAAa,CAAC,EACpB,EAAiB,QAAU,EAC3B,EAAgB,QAAU,EACvB,MAAM,EACN,MAAO,GAAmB,CACzB,QAAQ,MAAM,kCAAmC,8BAA+B,CAAK,CACvF,CAAC,EACA,YAAc,CACT,EAAiB,UAAY,IAC/B,EAAiB,QAAU,KAE/B,CAAC,CACL,CACF,EAAG,CAAC,EAAQ,EAAW,CAAS,CAAC,EAEjC,IAAM,EAAe,OACZ,CACL,SACA,QACA,YACA,cACA,YACA,QACA,eACA,WACF,GACA,CAAC,EAAQ,EAAO,EAAW,EAAa,EAAW,EAAO,EAAc,CAAS,CACnF,EAEA,OAAO,EAAC,EAAiB,SAAlB,CAA2B,MAAO,EAAe,UAAoC,CAAA,CAC9F,CAuCA,SAAgB,GAAe,CAC7B,IAAM,EAAU,EAAW,CAAgB,EAC3C,GAAI,CAAC,EACH,MAAU,MAAM,uDAAuD,EAEzE,OAAO,CACT,CCnaA,SAAgB,EACd,EACA,EACA,EACA,EAAyC,CAAC,EAC5B,CACd,OAAOA,EAAU,CACf,GAAG,EACH,OACA,cACA,YAAa,CACX,MAAO,YAAY,IACnB,aAAc,GACd,eAAgB,GAChB,gBAAiB,GACjB,cAAe,EACjB,EACA,QAAS,CACX,CAAC,CACH,CCnFA,SAAgB,GAAoD,CAClE,GAAI,OAAO,SAAa,KAAe,OAAO,UAAc,IAC1D,OAGF,IAAM,EAAe,SAAS,cAAgB,UAAU,aACxD,OAAO,GAAgB,EAAmB,CAAY,EAAI,EAAe,IAAA,EAC3E,CCNA,SAAgB,EACd,EACA,EAAU,GACD,CACT,GAAM,CAAC,EAAc,GAAmB,EAAS,EAAK,EAetD,OAbA,MAAgB,CACd,IAAM,EAAe,EAAU,EAAS,EAAI,IAAA,GAC5C,GAAI,CAAC,EAAc,CACjB,EAAgB,EAAK,EACrB,MACF,CAGA,OADA,EAAgB,EAAI,MACP,CACX,EAAa,WAAW,CAC1B,CACF,EAAG,CAAC,EAAU,CAAO,CAAC,EAEf,CACT,CC6CA,SAAgB,EAAgB,EAAgD,CAC9E,GAAM,CAAE,OAAM,eAAgB,EAExB,EAAY,EAAgB,CAAM,EA2BxC,MAAO,CAAE,aAAc,EAzBN,MAAkB,CACjC,IAAM,EAAe,EAAoB,EACzC,GAAI,CAAC,EAAc,CACjB,QAAQ,KACN,4DAA4D,EAAK,0BACnE,EACA,MACF,CAKA,GAAM,CAAE,cAAe,EAAU,QAC3B,EAAqB,EACvB,EAAqB,CAAU,EAAE,YACjC,IAAA,GAEJ,OAAO,EAAa,eAAe,CACjC,OACA,GAAI,IAAgB,IAAA,IAAa,CAAE,aAAY,EAC/C,GAAI,GAAsB,CAAE,WAAY,CAAmB,EAC3D,IAAK,KAAO,IAAS,EAAU,QAAQ,IAAI,CAAI,CACjD,CAAC,CACH,EAAG,CAAC,EAAM,EAAa,CAAS,CAEiB,EAAG,EAAO,OAAO,CAAE,CACtE,CClCA,SAAgB,EAAkB,EAAoD,CACpF,GAAM,CAAE,MAAK,OAAM,cAAa,WAAU,QAAS,EAE7C,EAAU,EAAgB,CAAI,EAoBpC,MAAO,CAAE,aAAc,EAlBN,MAAkB,CACjC,IAAM,EAAe,EAAoB,EACzC,GAAI,CAAC,EAAc,CACjB,QAAQ,KACN,8DAA8D,EAAI,0BACpE,EACA,MACF,CAEA,OAAO,EAAa,iBAAiB,CACnC,MACA,OACA,GAAI,IAAgB,IAAA,IAAa,CAAE,aAAY,EAC/C,GAAI,IAAa,IAAA,IAAa,CAAE,UAAS,EACzC,MAAO,EAAa,IAAW,EAAQ,QAAQ,EAAa,CAAM,CACpE,CAAC,CACH,EAAG,CAAC,EAAK,EAAM,EAAa,EAAU,CAAO,CAEI,EAAG,EAAO,OAAO,CAAE,CACtE"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/useWebMCP.ts","../src/useCommittedRef.ts","../src/client/McpClientProvider.tsx","../src/useWebMCPContext.ts","../src/model-context.ts","../src/useMcpRegistration.ts","../src/useWebMCPPrompt.ts","../src/useWebMCPResource.ts"],"sourcesContent":["'use client';\n\nimport { useMemo, type DependencyList } from 'react';\nimport {\n isMcpStandardSchema,\n normalizeInputSchema,\n normalizeToolResponse,\n type ToolInputSchema,\n} from '@mcp-b/webmcp-ts-sdk/schema';\nimport type { InputSchema, JsonSchemaForInference } from '@mcp-b/webmcp-ts-sdk';\nimport { useWebMCPWithAdapter, type WebMCPAdapter } from 'usewebmcp/internal';\nimport type { WebMCPConfig as CoreWebMCPConfig } from 'usewebmcp';\nimport type { InferOutput, InferValidatedToolInput, WebMCPConfig, WebMCPReturn } from './types.js';\n\nasync function validateInput<T extends ToolInputSchema>(\n schema: T | undefined,\n input: unknown\n): Promise<InferValidatedToolInput<T>> {\n if (!schema || !isMcpStandardSchema(schema)) {\n // SAFETY: plain JSON Schema carries no validator; MCP checks it on the wire and local callers type their input.\n return input as InferValidatedToolInput<T>;\n }\n\n const result = await schema['~standard'].validate(input);\n if ('value' in result) {\n // SAFETY: Standard Schema's successful result is its declared output; the guard preserves T.\n return result.value as InferValidatedToolInput<T>;\n }\n throw new TypeError(\n `Invalid tool input: ${(result.issues ?? []).map((issue) => issue.message).join('; ')}`\n );\n}\n\n/** The core React lifecycle with MCP result formatting and output metadata. */\nexport function useWebMCP<\n const TInput extends ToolInputSchema = InputSchema,\n const TOutput extends JsonSchemaForInference | undefined = undefined,\n>(config: WebMCPConfig<TInput, TOutput>, deps?: DependencyList): WebMCPReturn<TOutput, TInput> {\n const input = useMemo(() => {\n try {\n // The copy drops the validator normalizeInputSchema hides on the schema, so the MCP\n // server registers plain JSON Schema and only this hook runs the validator.\n return {\n schema: config.inputSchema && { ...normalizeInputSchema(config.inputSchema).inputSchema },\n };\n } catch (error) {\n return { error: error instanceof Error ? error : new Error(String(error)) };\n }\n }, [config.inputSchema]);\n const coreConfig: CoreWebMCPConfig<InputSchema, InferOutput<TOutput>> = {\n name: config.name,\n ...(config.title !== undefined && { title: config.title }),\n description: config.description,\n ...(input.schema !== undefined && { inputSchema: input.schema }),\n ...(config.enabled !== undefined && { enabled: config.enabled }),\n ...(config.exposedTo !== undefined && { exposedTo: config.exposedTo }),\n execute: async (args, options) => {\n const result = await config.execute(await validateInput(config.inputSchema, args), options);\n if (config.outputSchema && normalizeToolResponse(result).structuredContent === undefined) {\n throw new TypeError(\n `Tool \"${config.name}\" outputSchema requires execute to return a JSON-serializable result`\n );\n }\n return result;\n },\n };\n const adapter: WebMCPAdapter<InferOutput<TOutput>> = {\n descriptor: {\n ...(config.outputSchema !== undefined && { outputSchema: config.outputSchema }),\n ...(config.annotations !== undefined && { annotations: config.annotations }),\n },\n ...(input.error && { preparationError: input.error }),\n formatOutput: config.formatOutput ?? normalizeToolResponse,\n formatError:\n config.formatError ??\n ((error) => ({ content: [{ type: 'text', text: error.message }], isError: true })),\n };\n // SAFETY: the adapter validates Standard Schemas and the core registration uses the normalized JSON Schema.\n return useWebMCPWithAdapter(coreConfig, deps, adapter) as WebMCPReturn<TOutput, TInput>;\n}\n","import { useEffect, useLayoutEffect, useRef } from 'react';\n\nconst useIsomorphicLayoutEffect = globalThis.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 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\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 isCurrent = () =>\n connectionGeneration === connectionGenerationRef.current &&\n 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 if (!isCurrent()) {\n return;\n }\n\n setResources(resourceResponse?.resources ?? []);\n setTools(toolResponse?.tools ?? []);\n setError(null);\n } catch (cause) {\n if (!isCurrent()) {\n return;\n }\n const error = cause instanceof Error ? cause : new Error(String(cause));\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 const connectionGeneration = connectionGenerationRef.current;\n if (connectionStateRef.current !== 'connected' || freshTransport !== undefined) {\n if (connectionStateRef.current !== 'disconnected') {\n return;\n }\n\n connectionStateRef.current = 'connecting';\n setConnectionState('connecting');\n setError(null);\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 setCapabilities(client.getServerCapabilities() ?? null);\n }\n\n connectionStateRef.current = 'initializing';\n setConnectionState('initializing');\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 const resourcesListChanged = serverCapabilities?.resources?.listChanged === true;\n const toolsListChanged = serverCapabilities?.tools?.listChanged === true;\n if (!resourcesListChanged && !toolsListChanged) {\n return;\n }\n\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 controller = new AbortController();\n if (client.getProtocolEra() === 'modern') {\n void client\n .listen(\n {\n ...(toolsListChanged && { toolsListChanged: true }),\n ...(resourcesListChanged && { resourcesListChanged: true }),\n },\n { signal: controller.signal }\n )\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 } else {\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 controller.abort();\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((cause: unknown) => {\n console.error('[ReactWebMCP:McpClientProvider]', 'Failed to close MCP client:', cause);\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 type { WebMCPConfig, WebMCPReturn } from './types.js';\nimport { useWebMCP } from './useWebMCP.js';\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 type { BrowserMcpServer } from '@mcp-b/webmcp-ts-sdk';\n\n// The SDK exports this guard from its root entry, which would bundle the MCP server into clients.\nfunction isBrowserMcpServer(context: EventTarget): context is BrowserMcpServer {\n return '__isBrowserMcpServer' in context && context.__isBrowserMcpServer === true;\n}\n\nexport function getBrowserMcpServer(): BrowserMcpServer | undefined {\n const modelContext = globalThis.document?.modelContext;\n return modelContext && isBrowserMcpServer(modelContext) ? modelContext : undefined;\n}\n","import type { RegistrationHandle } from '@mcp-b/webmcp-ts-sdk';\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-ts-sdk/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 // Read at registration time so an inline `argsSchema` literal cannot re-register every\n // render; a changed schema takes effect at the next registration.\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.1.0",
3
+ "version": "6.0.0-beta.20261001010549",
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,12 +61,12 @@
61
61
  },
62
62
  "dependencies": {
63
63
  "@modelcontextprotocol/client": "2.0.0",
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"
64
+ "@standard-schema/spec": "^1.1.0",
65
+ "@mcp-b/webmcp-ts-sdk": "6.0.0-beta.20261001010549",
66
+ "usewebmcp": "6.0.0-beta.20261001010549"
68
67
  },
69
68
  "devDependencies": {
69
+ "@modelcontextprotocol/core": "2.0.0",
70
70
  "@modelcontextprotocol/server": "2.0.0",
71
71
  "@types/node": "22.20.1",
72
72
  "@types/react": "^19.2.18",
@@ -78,8 +78,9 @@
78
78
  "vite-plus": "0.1.24",
79
79
  "vitest": "npm:@voidzero-dev/vite-plus-test@0.1.24",
80
80
  "vitest-browser-react": "^2.2.0",
81
- "@mcp-b/global": "5.1.0",
82
- "@mcp-b/transports": "5.1.0"
81
+ "zod": "4.4.3",
82
+ "@mcp-b/transports": "6.0.0-beta.20261001010549",
83
+ "@mcp-b/global": "6.0.0-beta.20261001010549"
83
84
  },
84
85
  "peerDependencies": {
85
86
  "react": "^18.0.0 || ^19.0.0"