@mcp-b/react-webmcp 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +478 -0
- package/dist/index.d.ts +449 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 mcp-b contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
# @mcp-b/react-webmcp
|
|
2
|
+
|
|
3
|
+
Complete React hooks for the Model Context Protocol - register tools via `navigator.modelContext` and consume tools from MCP servers.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### Provider Hooks (Registering Tools)
|
|
8
|
+
- **Type-Safe**: Full TypeScript support with Zod schema validation
|
|
9
|
+
- **State-Aware**: Track execution state (loading, success, error) for UI feedback
|
|
10
|
+
- **React-Native**: Designed for React's lifecycle, including StrictMode compatibility
|
|
11
|
+
- **Developer-Friendly**: Intuitive API with comprehensive examples
|
|
12
|
+
|
|
13
|
+
### Client Hooks (Consuming Tools)
|
|
14
|
+
- **MCP Client Provider**: Connect to MCP servers and consume their tools
|
|
15
|
+
- **Real-time Updates**: Listen for tool list changes via MCP notifications
|
|
16
|
+
- **Connection Management**: Automatic connection handling with reconnect support
|
|
17
|
+
- **Type-Safe Tool Calls**: Full TypeScript support for client operations
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add @mcp-b/react-webmcp zod
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For client functionality, you'll also need:
|
|
26
|
+
```bash
|
|
27
|
+
pnpm add @mcp-b/transports @modelcontextprotocol/sdk
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Prerequisites
|
|
31
|
+
|
|
32
|
+
**For Provider Hooks:** Requires the global `navigator.modelContext` API. Install `@mcp-b/global` or use a browser that implements the Web Model Context API.
|
|
33
|
+
|
|
34
|
+
**For Client Hooks:** Requires an MCP server to connect to (e.g., one created with `@mcp-b/global` or the Model Context Protocol SDK).
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
# Part 1: Provider API (Registering Tools)
|
|
39
|
+
|
|
40
|
+
Use these hooks to expose tools from your React app that AI agents can discover and call.
|
|
41
|
+
|
|
42
|
+
## Quick Start - Provider
|
|
43
|
+
|
|
44
|
+
### Basic Tool Registration
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { useWebMCP } from '@mcp-b/react-webmcp';
|
|
48
|
+
import { z } from 'zod';
|
|
49
|
+
|
|
50
|
+
function PostsPage() {
|
|
51
|
+
const likeTool = useWebMCP({
|
|
52
|
+
name: 'posts_like',
|
|
53
|
+
description: 'Like a post by ID. Increments the like count.',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
postId: z.string().uuid().describe('The post ID to like'),
|
|
56
|
+
},
|
|
57
|
+
annotations: {
|
|
58
|
+
title: 'Like Post',
|
|
59
|
+
readOnlyHint: false,
|
|
60
|
+
idempotentHint: true,
|
|
61
|
+
},
|
|
62
|
+
handler: async (input) => {
|
|
63
|
+
await api.posts.like(input.postId);
|
|
64
|
+
return { success: true, postId: input.postId };
|
|
65
|
+
},
|
|
66
|
+
formatOutput: (result) => `Post ${result.postId} liked successfully!`,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div>
|
|
71
|
+
{likeTool.state.isExecuting && <Spinner />}
|
|
72
|
+
{likeTool.state.error && <ErrorAlert error={likeTool.state.error} />}
|
|
73
|
+
{/* Your UI */}
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Context Tool
|
|
80
|
+
|
|
81
|
+
Expose read-only context to AI:
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { useWebMCPContext } from '@mcp-b/react-webmcp';
|
|
85
|
+
|
|
86
|
+
function PostDetailPage() {
|
|
87
|
+
const { postId } = useParams();
|
|
88
|
+
const { data: post } = useQuery(['post', postId], () => fetchPost(postId));
|
|
89
|
+
|
|
90
|
+
useWebMCPContext(
|
|
91
|
+
'context_current_post',
|
|
92
|
+
'Get the currently viewed post ID and metadata',
|
|
93
|
+
() => ({
|
|
94
|
+
postId,
|
|
95
|
+
title: post?.title,
|
|
96
|
+
author: post?.author,
|
|
97
|
+
tags: post?.tags,
|
|
98
|
+
})
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
return <div>{/* Post UI */}</div>;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Provider API Reference
|
|
106
|
+
|
|
107
|
+
### `useWebMCP`
|
|
108
|
+
|
|
109
|
+
Main hook for registering MCP tools with full control over behavior and state.
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
function useWebMCP<
|
|
113
|
+
TInputSchema extends Record<string, z.ZodTypeAny>,
|
|
114
|
+
TOutput = string
|
|
115
|
+
>(config: WebMCPConfig<TInputSchema, TOutput>): WebMCPReturn<TOutput>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### Configuration Options
|
|
119
|
+
|
|
120
|
+
| Option | Type | Required | Description |
|
|
121
|
+
|--------|------|----------|-------------|
|
|
122
|
+
| `name` | `string` | ✓ | Unique tool identifier (e.g., 'posts_like') |
|
|
123
|
+
| `description` | `string` | ✓ | Human-readable description for AI |
|
|
124
|
+
| `inputSchema` | `Record<string, ZodType>` | - | Input validation using Zod schemas |
|
|
125
|
+
| `outputSchema` | `Record<string, ZodType>` | - | Output validation (optional) |
|
|
126
|
+
| `annotations` | `ToolAnnotations` | - | Metadata hints for the AI |
|
|
127
|
+
| `elicitation` | `ElicitationConfig` | - | User confirmation settings |
|
|
128
|
+
| `handler` | `(input) => Promise<TOutput>` | ✓ | Function that executes the tool |
|
|
129
|
+
| `formatOutput` | `(output) => string` | - | Custom output formatter |
|
|
130
|
+
| `onError` | `(error, input) => void` | - | Error handler callback |
|
|
131
|
+
|
|
132
|
+
#### Return Value
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
interface WebMCPReturn<TOutput> {
|
|
136
|
+
state: {
|
|
137
|
+
isExecuting: boolean; // Currently running
|
|
138
|
+
lastResult: TOutput | null; // Last successful result
|
|
139
|
+
error: Error | null; // Last error
|
|
140
|
+
executionCount: number; // Total executions
|
|
141
|
+
};
|
|
142
|
+
execute: (input: unknown) => Promise<TOutput>; // Manual execution
|
|
143
|
+
reset: () => void; // Reset state
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### `useWebMCPContext`
|
|
148
|
+
|
|
149
|
+
Simplified hook for read-only context exposure:
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
function useWebMCPContext<T>(
|
|
153
|
+
name: string,
|
|
154
|
+
description: string,
|
|
155
|
+
getValue: () => T
|
|
156
|
+
): WebMCPReturn<T>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
# Part 2: Client API (Consuming Tools)
|
|
162
|
+
|
|
163
|
+
Use these hooks to connect to MCP servers and call their tools from your React app.
|
|
164
|
+
|
|
165
|
+
## Quick Start - Client
|
|
166
|
+
|
|
167
|
+
### Connecting to an MCP Server
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import { McpClientProvider, useMcpClient } from '@mcp-b/react-webmcp';
|
|
171
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
172
|
+
import { TabClientTransport } from '@mcp-b/transports';
|
|
173
|
+
|
|
174
|
+
// Create client and transport
|
|
175
|
+
const client = new Client({ name: 'MyApp', version: '1.0.0' });
|
|
176
|
+
const transport = new TabClientTransport('mcp', { clientInstanceId: 'my-app' });
|
|
177
|
+
|
|
178
|
+
function App() {
|
|
179
|
+
return (
|
|
180
|
+
<McpClientProvider client={client} transport={transport}>
|
|
181
|
+
<ToolConsumer />
|
|
182
|
+
</McpClientProvider>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function ToolConsumer() {
|
|
187
|
+
const { client, tools, isConnected, capabilities } = useMcpClient();
|
|
188
|
+
|
|
189
|
+
const handleCallTool = async () => {
|
|
190
|
+
const result = await client.callTool({
|
|
191
|
+
name: 'posts_like',
|
|
192
|
+
arguments: { postId: '123' }
|
|
193
|
+
});
|
|
194
|
+
console.log('Result:', result.content[0].text);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<div>
|
|
199
|
+
<p>Connected: {isConnected ? 'Yes' : 'No'}</p>
|
|
200
|
+
<p>Available Tools: {tools.length}</p>
|
|
201
|
+
<ul>
|
|
202
|
+
{tools.map(tool => (
|
|
203
|
+
<li key={tool.name}>{tool.name} - {tool.description}</li>
|
|
204
|
+
))}
|
|
205
|
+
</ul>
|
|
206
|
+
<button onClick={handleCallTool} disabled={!isConnected}>
|
|
207
|
+
Call Tool
|
|
208
|
+
</button>
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Listening for Tool List Changes
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
function ToolList() {
|
|
218
|
+
const { tools, isConnected, capabilities } = useMcpClient();
|
|
219
|
+
|
|
220
|
+
// Tools automatically update when server sends notifications
|
|
221
|
+
// if capabilities.tools.listChanged is true
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<div>
|
|
225
|
+
<h3>Tools ({tools.length})</h3>
|
|
226
|
+
{capabilities?.tools?.listChanged && (
|
|
227
|
+
<p>✓ Server supports real-time tool updates</p>
|
|
228
|
+
)}
|
|
229
|
+
{tools.map(tool => (
|
|
230
|
+
<div key={tool.name}>
|
|
231
|
+
<h4>{tool.name}</h4>
|
|
232
|
+
<p>{tool.description}</p>
|
|
233
|
+
</div>
|
|
234
|
+
))}
|
|
235
|
+
</div>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Client API Reference
|
|
241
|
+
|
|
242
|
+
### `McpClientProvider`
|
|
243
|
+
|
|
244
|
+
Provider component that manages an MCP client connection.
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
interface McpClientProviderProps {
|
|
248
|
+
children: ReactNode;
|
|
249
|
+
client: Client; // MCP client instance
|
|
250
|
+
transport: Transport; // Transport for connection
|
|
251
|
+
opts?: RequestOptions; // Optional connection options
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### Example Transports
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
// Connect to same-page MCP server (via @mcp-b/global)
|
|
259
|
+
import { TabClientTransport } from '@mcp-b/transports';
|
|
260
|
+
const transport = new TabClientTransport('mcp', { clientInstanceId: 'my-app' });
|
|
261
|
+
|
|
262
|
+
// Connect to Chrome extension MCP server
|
|
263
|
+
import { ExtensionClientTransport } from '@mcp-b/transports';
|
|
264
|
+
const transport = new ExtensionClientTransport({ portName: 'mcp' });
|
|
265
|
+
|
|
266
|
+
// In-memory connection (for testing)
|
|
267
|
+
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
|
268
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### `useMcpClient`
|
|
272
|
+
|
|
273
|
+
Hook to access the MCP client context. Must be used within `McpClientProvider`.
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
interface McpClientContextValue {
|
|
277
|
+
client: Client; // MCP client instance
|
|
278
|
+
tools: Tool[]; // Available tools from server
|
|
279
|
+
resources: Resource[]; // Available resources
|
|
280
|
+
isConnected: boolean; // Connection status
|
|
281
|
+
isLoading: boolean; // Currently connecting
|
|
282
|
+
error: Error | null; // Connection error
|
|
283
|
+
capabilities: ServerCapabilities | null; // Server capabilities
|
|
284
|
+
reconnect: () => Promise<void>; // Manual reconnection
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
#### Calling Tools
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
function MyComponent() {
|
|
292
|
+
const { client, isConnected } = useMcpClient();
|
|
293
|
+
|
|
294
|
+
const callTool = async () => {
|
|
295
|
+
if (!isConnected) return;
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
const result = await client.callTool({
|
|
299
|
+
name: 'my_tool',
|
|
300
|
+
arguments: { foo: 'bar' }
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Extract text from result
|
|
304
|
+
const text = result.content
|
|
305
|
+
.filter(c => c.type === 'text')
|
|
306
|
+
.map(c => c.text)
|
|
307
|
+
.join('\n');
|
|
308
|
+
|
|
309
|
+
console.log(text);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
console.error('Tool call failed:', error);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
return <button onClick={callTool}>Call Tool</button>;
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
# Complete Example: Both Provider and Client
|
|
322
|
+
|
|
323
|
+
This example shows a React app that both exposes tools AND consumes tools from an MCP server.
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
import '@mcp-b/global'; // Provides navigator.modelContext
|
|
327
|
+
import { McpClientProvider, useWebMCP, useMcpClient } from '@mcp-b/react-webmcp';
|
|
328
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
329
|
+
import { TabClientTransport } from '@mcp-b/transports';
|
|
330
|
+
import { z } from 'zod';
|
|
331
|
+
|
|
332
|
+
// Create client to consume tools
|
|
333
|
+
const client = new Client({ name: 'MyApp', version: '1.0.0' });
|
|
334
|
+
const transport = new TabClientTransport('mcp', { clientInstanceId: 'my-app' });
|
|
335
|
+
|
|
336
|
+
function App() {
|
|
337
|
+
return (
|
|
338
|
+
<McpClientProvider client={client} transport={transport}>
|
|
339
|
+
<ToolProvider />
|
|
340
|
+
<ToolConsumer />
|
|
341
|
+
</McpClientProvider>
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Component that REGISTERS tools
|
|
346
|
+
function ToolProvider() {
|
|
347
|
+
const [count, setCount] = useState(0);
|
|
348
|
+
|
|
349
|
+
// Expose a tool that increments the counter
|
|
350
|
+
useWebMCP({
|
|
351
|
+
name: 'increment_counter',
|
|
352
|
+
description: 'Increment the counter',
|
|
353
|
+
inputSchema: {
|
|
354
|
+
amount: z.number().default(1)
|
|
355
|
+
},
|
|
356
|
+
handler: async ({ amount }) => {
|
|
357
|
+
setCount(prev => prev + amount);
|
|
358
|
+
return { newValue: count + amount };
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
return <div>Counter: {count}</div>;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Component that CONSUMES tools
|
|
366
|
+
function ToolConsumer() {
|
|
367
|
+
const { client, tools, isConnected } = useMcpClient();
|
|
368
|
+
const [result, setResult] = useState('');
|
|
369
|
+
|
|
370
|
+
const callIncrementTool = async () => {
|
|
371
|
+
const res = await client.callTool({
|
|
372
|
+
name: 'increment_counter',
|
|
373
|
+
arguments: { amount: 5 }
|
|
374
|
+
});
|
|
375
|
+
setResult(res.content[0].text);
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
<div>
|
|
380
|
+
<p>Available Tools: {tools.map(t => t.name).join(', ')}</p>
|
|
381
|
+
<button onClick={callIncrementTool} disabled={!isConnected}>
|
|
382
|
+
Call increment_counter Tool
|
|
383
|
+
</button>
|
|
384
|
+
{result && <p>Result: {result}</p>}
|
|
385
|
+
</div>
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
# Migration from @mcp-b/mcp-react-hooks
|
|
393
|
+
|
|
394
|
+
If you're migrating from the deprecated `@mcp-b/mcp-react-hooks` package:
|
|
395
|
+
|
|
396
|
+
## What Changed
|
|
397
|
+
|
|
398
|
+
- **Server providers removed**: `McpServerProvider` and `McpMemoryProvider` are gone
|
|
399
|
+
- **Everything in one package**: Both client and provider hooks are now in `@mcp-b/react-webmcp`
|
|
400
|
+
- **Tool registration**: Use `useWebMCP` instead of server providers
|
|
401
|
+
- **Client unchanged**: `McpClientProvider` and `useMcpClient` work the same way
|
|
402
|
+
|
|
403
|
+
## Migration Guide
|
|
404
|
+
|
|
405
|
+
### Before (mcp-react-hooks)
|
|
406
|
+
|
|
407
|
+
```tsx
|
|
408
|
+
import { McpClientProvider, useMcpClient } from '@mcp-b/mcp-react-hooks';
|
|
409
|
+
import { McpServerProvider, useMcpServer } from '@mcp-b/mcp-react-hooks';
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### After (react-webmcp)
|
|
413
|
+
|
|
414
|
+
```tsx
|
|
415
|
+
// Client hooks - same API
|
|
416
|
+
import { McpClientProvider, useMcpClient } from '@mcp-b/react-webmcp';
|
|
417
|
+
|
|
418
|
+
// For registering tools, use useWebMCP instead of server providers
|
|
419
|
+
import { useWebMCP } from '@mcp-b/react-webmcp';
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Converting Server to Provider
|
|
423
|
+
|
|
424
|
+
**Before:**
|
|
425
|
+
```tsx
|
|
426
|
+
function MyApp() {
|
|
427
|
+
const { registerTool } = useMcpServer();
|
|
428
|
+
|
|
429
|
+
useEffect(() => {
|
|
430
|
+
const tool = registerTool('my_tool', { description: '...' }, handler);
|
|
431
|
+
return () => tool.remove();
|
|
432
|
+
}, []);
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**After:**
|
|
437
|
+
```tsx
|
|
438
|
+
function MyApp() {
|
|
439
|
+
useWebMCP({
|
|
440
|
+
name: 'my_tool',
|
|
441
|
+
description: '...',
|
|
442
|
+
handler: handler
|
|
443
|
+
});
|
|
444
|
+
// Auto-registers and cleans up on unmount
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
# Best Practices
|
|
451
|
+
|
|
452
|
+
### Tool Naming
|
|
453
|
+
- Use verb-noun format: `posts_like`, `graph_navigate`, `table_filter`
|
|
454
|
+
- Prefix with domain: `posts_`, `comments_`, `graph_`
|
|
455
|
+
- Be specific and descriptive
|
|
456
|
+
|
|
457
|
+
### Annotations
|
|
458
|
+
- Always set `readOnlyHint` (true for queries, false for mutations)
|
|
459
|
+
- Set `idempotentHint` (true if repeated calls are safe)
|
|
460
|
+
- Set `destructiveHint` for delete/permanent operations
|
|
461
|
+
|
|
462
|
+
### Error Handling
|
|
463
|
+
- Throw descriptive errors from tool handlers
|
|
464
|
+
- Use `onError` callback for side effects (logging, toasts)
|
|
465
|
+
- Handle connection errors in client components
|
|
466
|
+
|
|
467
|
+
### Performance
|
|
468
|
+
- Tools automatically prevent duplicate registration in React StrictMode
|
|
469
|
+
- Use `useWebMCPContext` for lightweight read-only data exposure
|
|
470
|
+
- Client automatically manages reconnection and tool list updates
|
|
471
|
+
|
|
472
|
+
## License
|
|
473
|
+
|
|
474
|
+
MIT
|
|
475
|
+
|
|
476
|
+
## Contributing
|
|
477
|
+
|
|
478
|
+
See the [main repository](https://github.com/WebMCP-org/WebMCP) for contribution guidelines.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import { ReactElement, ReactNode } from "react";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { CallToolResult, Resource, Resource as Resource$1, ServerCapabilities, ServerCapabilities as ServerCapabilities$1, Tool, Tool as Tool$1 } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { ToolAnnotations } from "@mcp-b/webmcp-ts-sdk";
|
|
5
|
+
import { ModelContext as ModelContextProtocol, ToolDescriptor } from "@mcp-b/global";
|
|
6
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
7
|
+
import { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
8
|
+
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
9
|
+
|
|
10
|
+
//#region src/types.d.ts
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Represents the current execution state of a tool, including loading status,
|
|
14
|
+
* results, errors, and execution history.
|
|
15
|
+
*
|
|
16
|
+
* @template TOutput - The type of data returned by the tool handler
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
interface ToolExecutionState<TOutput = unknown> {
|
|
20
|
+
/**
|
|
21
|
+
* Indicates whether the tool is currently executing.
|
|
22
|
+
* Use this to show loading states in your UI.
|
|
23
|
+
*/
|
|
24
|
+
isExecuting: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The result from the most recent successful execution.
|
|
27
|
+
* Will be `null` if the tool hasn't been executed or last execution failed.
|
|
28
|
+
*/
|
|
29
|
+
lastResult: TOutput | null;
|
|
30
|
+
/**
|
|
31
|
+
* The error from the most recent failed execution.
|
|
32
|
+
* Will be `null` if the tool hasn't been executed or last execution succeeded.
|
|
33
|
+
*/
|
|
34
|
+
error: Error | null;
|
|
35
|
+
/**
|
|
36
|
+
* Total number of times this tool has been executed.
|
|
37
|
+
* Increments on both successful and failed executions.
|
|
38
|
+
*/
|
|
39
|
+
executionCount: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Configuration options for the `useWebMCP` hook.
|
|
43
|
+
* Defines a tool's metadata, schema, handler, and lifecycle callbacks.
|
|
44
|
+
*
|
|
45
|
+
* @template TInputSchema - Zod schema object defining input parameters
|
|
46
|
+
* @template TOutput - The type of data returned by the handler function
|
|
47
|
+
* @public
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const config: WebMCPConfig = {
|
|
52
|
+
* name: 'posts_like',
|
|
53
|
+
* description: 'Like a post by its ID',
|
|
54
|
+
* inputSchema: {
|
|
55
|
+
* postId: z.string().uuid(),
|
|
56
|
+
* },
|
|
57
|
+
* handler: async ({ postId }) => {
|
|
58
|
+
* await api.likePost(postId);
|
|
59
|
+
* return { success: true };
|
|
60
|
+
* },
|
|
61
|
+
* };
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
interface WebMCPConfig<TInputSchema extends Record<string, z.ZodTypeAny> = Record<string, never>, TOutput = string> {
|
|
65
|
+
/**
|
|
66
|
+
* Unique identifier for the tool (e.g., 'posts_like', 'graph_navigate').
|
|
67
|
+
* Must follow naming conventions: lowercase with underscores.
|
|
68
|
+
*/
|
|
69
|
+
name: string;
|
|
70
|
+
/**
|
|
71
|
+
* Human-readable description explaining what the tool does.
|
|
72
|
+
* This description is used by AI assistants to understand when to use the tool.
|
|
73
|
+
*/
|
|
74
|
+
description: string;
|
|
75
|
+
/**
|
|
76
|
+
* Zod schema object defining the input parameters for the tool.
|
|
77
|
+
* Each key is a parameter name, and the value is a Zod type definition.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```typescript
|
|
81
|
+
* inputSchema: {
|
|
82
|
+
* postId: z.string().uuid().describe('The ID of the post to like'),
|
|
83
|
+
* userId: z.string().optional(),
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
inputSchema?: TInputSchema;
|
|
88
|
+
/**
|
|
89
|
+
* Optional Zod schema object defining the expected output structure.
|
|
90
|
+
* Used for runtime validation of handler return values.
|
|
91
|
+
*/
|
|
92
|
+
outputSchema?: Record<string, z.ZodTypeAny>;
|
|
93
|
+
/**
|
|
94
|
+
* Optional metadata annotations providing hints about tool behavior.
|
|
95
|
+
* See {@link ToolAnnotations} for available options.
|
|
96
|
+
*/
|
|
97
|
+
annotations?: ToolAnnotations;
|
|
98
|
+
/**
|
|
99
|
+
* The function that executes when the tool is called.
|
|
100
|
+
* Can be synchronous or asynchronous.
|
|
101
|
+
*
|
|
102
|
+
* @param input - Validated input parameters matching the inputSchema
|
|
103
|
+
* @returns The result data or a Promise resolving to the result
|
|
104
|
+
*/
|
|
105
|
+
handler: (input: z.infer<z.ZodObject<TInputSchema>>) => Promise<TOutput> | TOutput;
|
|
106
|
+
/**
|
|
107
|
+
* Optional function to format the handler output for the MCP response.
|
|
108
|
+
* Defaults to JSON.stringify with indentation.
|
|
109
|
+
*
|
|
110
|
+
* @param output - The raw output from the handler
|
|
111
|
+
* @returns Formatted string for the MCP response
|
|
112
|
+
*/
|
|
113
|
+
formatOutput?: (output: TOutput) => string;
|
|
114
|
+
/**
|
|
115
|
+
* Optional callback invoked when the tool execution succeeds.
|
|
116
|
+
* Useful for triggering side effects like navigation or analytics.
|
|
117
|
+
*
|
|
118
|
+
* @param result - The successful result from the handler
|
|
119
|
+
* @param input - The input that was passed to the handler
|
|
120
|
+
*/
|
|
121
|
+
onSuccess?: (result: TOutput, input: unknown) => void;
|
|
122
|
+
/**
|
|
123
|
+
* Optional callback invoked when the tool execution fails.
|
|
124
|
+
* Useful for error handling, logging, or showing user notifications.
|
|
125
|
+
*
|
|
126
|
+
* @param error - The error that occurred during execution
|
|
127
|
+
* @param input - The input that was passed to the handler
|
|
128
|
+
*/
|
|
129
|
+
onError?: (error: Error, input: unknown) => void;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Return value from the `useWebMCP` hook.
|
|
133
|
+
* Provides access to execution state and methods for manual tool control.
|
|
134
|
+
*
|
|
135
|
+
* @template TOutput - The type of data returned by the tool handler
|
|
136
|
+
* @public
|
|
137
|
+
*/
|
|
138
|
+
interface WebMCPReturn<TOutput = unknown> {
|
|
139
|
+
/**
|
|
140
|
+
* Current execution state including loading status, results, and errors.
|
|
141
|
+
* See {@link ToolExecutionState} for details.
|
|
142
|
+
*/
|
|
143
|
+
state: ToolExecutionState<TOutput>;
|
|
144
|
+
/**
|
|
145
|
+
* Manually execute the tool with the provided input.
|
|
146
|
+
* Useful for testing, debugging, or triggering execution from your UI.
|
|
147
|
+
*
|
|
148
|
+
* @param input - The input parameters to pass to the tool
|
|
149
|
+
* @returns Promise resolving to the tool's output
|
|
150
|
+
* @throws Error if validation fails or handler throws
|
|
151
|
+
*/
|
|
152
|
+
execute: (input: unknown) => Promise<TOutput>;
|
|
153
|
+
/**
|
|
154
|
+
* Reset the execution state to its initial values.
|
|
155
|
+
* Clears results, errors, and resets the execution count.
|
|
156
|
+
*/
|
|
157
|
+
reset: () => void;
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/useWebMCP.d.ts
|
|
161
|
+
/**
|
|
162
|
+
* React hook for registering and managing Model Context Protocol (MCP) tools.
|
|
163
|
+
*
|
|
164
|
+
* This hook handles the complete lifecycle of an MCP tool:
|
|
165
|
+
* - Registers the tool with `window.navigator.modelContext`
|
|
166
|
+
* - Manages execution state (loading, results, errors)
|
|
167
|
+
* - Validates input using Zod schemas
|
|
168
|
+
* - Handles tool execution and lifecycle callbacks
|
|
169
|
+
* - Automatically unregisters on component unmount
|
|
170
|
+
*
|
|
171
|
+
* @template TInputSchema - Zod schema object defining input parameter types
|
|
172
|
+
* @template TOutput - Type of data returned by the handler function
|
|
173
|
+
*
|
|
174
|
+
* @param config - Configuration object for the tool
|
|
175
|
+
* @returns Object containing execution state and control methods
|
|
176
|
+
*
|
|
177
|
+
* @public
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* Basic tool registration:
|
|
181
|
+
* ```tsx
|
|
182
|
+
* function PostActions() {
|
|
183
|
+
* const likeTool = useWebMCP({
|
|
184
|
+
* name: 'posts_like',
|
|
185
|
+
* description: 'Like a post by ID',
|
|
186
|
+
* inputSchema: {
|
|
187
|
+
* postId: z.string().uuid().describe('The ID of the post to like'),
|
|
188
|
+
* },
|
|
189
|
+
* handler: async ({ postId }) => {
|
|
190
|
+
* await api.posts.like(postId);
|
|
191
|
+
* return { success: true, postId };
|
|
192
|
+
* },
|
|
193
|
+
* });
|
|
194
|
+
*
|
|
195
|
+
* if (likeTool.state.isExecuting) {
|
|
196
|
+
* return <Spinner />;
|
|
197
|
+
* }
|
|
198
|
+
*
|
|
199
|
+
* return <div>Post actions ready</div>;
|
|
200
|
+
* }
|
|
201
|
+
* ```
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* Tool with annotations and callbacks:
|
|
205
|
+
* ```tsx
|
|
206
|
+
* const deleteTool = useWebMCP({
|
|
207
|
+
* name: 'posts_delete',
|
|
208
|
+
* description: 'Delete a post permanently',
|
|
209
|
+
* inputSchema: {
|
|
210
|
+
* postId: z.string().uuid(),
|
|
211
|
+
* },
|
|
212
|
+
* annotations: {
|
|
213
|
+
* destructiveHint: true,
|
|
214
|
+
* idempotentHint: false,
|
|
215
|
+
* },
|
|
216
|
+
* handler: async ({ postId }) => {
|
|
217
|
+
* await api.posts.delete(postId);
|
|
218
|
+
* return { deleted: true };
|
|
219
|
+
* },
|
|
220
|
+
* onSuccess: () => {
|
|
221
|
+
* navigate('/posts');
|
|
222
|
+
* toast.success('Post deleted');
|
|
223
|
+
* },
|
|
224
|
+
* onError: (error) => {
|
|
225
|
+
* toast.error(`Failed to delete: ${error.message}`);
|
|
226
|
+
* },
|
|
227
|
+
* });
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
declare function useWebMCP<TInputSchema extends Record<string, z.ZodTypeAny> = Record<string, never>, TOutput = string>(config: WebMCPConfig<TInputSchema, TOutput>): WebMCPReturn<TOutput>;
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/useWebMCPContext.d.ts
|
|
233
|
+
/**
|
|
234
|
+
* Convenience hook for exposing read-only context data to AI assistants.
|
|
235
|
+
*
|
|
236
|
+
* This is a simplified wrapper around {@link useWebMCP} specifically designed for
|
|
237
|
+
* context tools that expose data without performing actions. The hook automatically
|
|
238
|
+
* configures appropriate annotations (read-only, idempotent) and handles value
|
|
239
|
+
* serialization.
|
|
240
|
+
*
|
|
241
|
+
* @template T - The type of context data to expose
|
|
242
|
+
*
|
|
243
|
+
* @param name - Unique identifier for the context tool (e.g., 'context_current_post')
|
|
244
|
+
* @param description - Human-readable description of the context for AI assistants
|
|
245
|
+
* @param getValue - Function that returns the current context value
|
|
246
|
+
* @returns Tool execution state and control methods
|
|
247
|
+
*
|
|
248
|
+
* @public
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* Expose current post context:
|
|
252
|
+
* ```tsx
|
|
253
|
+
* function PostDetailPage() {
|
|
254
|
+
* const { postId } = useParams();
|
|
255
|
+
* const { data: post } = useQuery(['post', postId], () => fetchPost(postId));
|
|
256
|
+
*
|
|
257
|
+
* useWebMCPContext(
|
|
258
|
+
* 'context_current_post',
|
|
259
|
+
* 'Get the currently viewed post ID and metadata',
|
|
260
|
+
* () => ({
|
|
261
|
+
* postId,
|
|
262
|
+
* title: post?.title,
|
|
263
|
+
* author: post?.author,
|
|
264
|
+
* tags: post?.tags,
|
|
265
|
+
* createdAt: post?.createdAt,
|
|
266
|
+
* })
|
|
267
|
+
* );
|
|
268
|
+
*
|
|
269
|
+
* return <PostContent post={post} />;
|
|
270
|
+
* }
|
|
271
|
+
* ```
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* Expose user session context:
|
|
275
|
+
* ```tsx
|
|
276
|
+
* function AppRoot() {
|
|
277
|
+
* const { user, isAuthenticated } = useAuth();
|
|
278
|
+
*
|
|
279
|
+
* useWebMCPContext(
|
|
280
|
+
* 'context_user_session',
|
|
281
|
+
* 'Get the current user session information',
|
|
282
|
+
* () => ({
|
|
283
|
+
* isAuthenticated,
|
|
284
|
+
* userId: user?.id,
|
|
285
|
+
* email: user?.email,
|
|
286
|
+
* permissions: user?.permissions,
|
|
287
|
+
* })
|
|
288
|
+
* );
|
|
289
|
+
*
|
|
290
|
+
* return <App />;
|
|
291
|
+
* }
|
|
292
|
+
* ```
|
|
293
|
+
*/
|
|
294
|
+
declare function useWebMCPContext<T>(name: string, description: string, getValue: () => T): WebMCPReturn<T>;
|
|
295
|
+
//#endregion
|
|
296
|
+
//#region src/client/McpClientProvider.d.ts
|
|
297
|
+
/**
|
|
298
|
+
* Context value provided by McpClientProvider.
|
|
299
|
+
*
|
|
300
|
+
* @internal
|
|
301
|
+
*/
|
|
302
|
+
interface McpClientContextValue {
|
|
303
|
+
client: Client;
|
|
304
|
+
tools: Tool$1[];
|
|
305
|
+
resources: Resource$1[];
|
|
306
|
+
isConnected: boolean;
|
|
307
|
+
isLoading: boolean;
|
|
308
|
+
error: Error | null;
|
|
309
|
+
capabilities: ServerCapabilities$1 | null;
|
|
310
|
+
reconnect: () => Promise<void>;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Props for the McpClientProvider component.
|
|
314
|
+
*
|
|
315
|
+
* @public
|
|
316
|
+
*/
|
|
317
|
+
interface McpClientProviderProps {
|
|
318
|
+
/**
|
|
319
|
+
* React children to render within the provider.
|
|
320
|
+
*/
|
|
321
|
+
children: ReactNode;
|
|
322
|
+
/**
|
|
323
|
+
* MCP Client instance to use for communication.
|
|
324
|
+
*/
|
|
325
|
+
client: Client;
|
|
326
|
+
/**
|
|
327
|
+
* Transport instance for the client to connect through.
|
|
328
|
+
*/
|
|
329
|
+
transport: Transport;
|
|
330
|
+
/**
|
|
331
|
+
* Optional request options for the connection.
|
|
332
|
+
*/
|
|
333
|
+
opts?: RequestOptions;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Provider component that manages an MCP client connection and exposes
|
|
337
|
+
* tools, resources, and connection state to child components.
|
|
338
|
+
*
|
|
339
|
+
* This provider handles:
|
|
340
|
+
* - Establishing and maintaining the MCP client connection
|
|
341
|
+
* - Fetching available tools and resources from the server
|
|
342
|
+
* - Listening for server notifications about tool/resource changes
|
|
343
|
+
* - Managing connection state and errors
|
|
344
|
+
* - Automatic cleanup on unmount
|
|
345
|
+
*
|
|
346
|
+
* @param props - Component props
|
|
347
|
+
* @returns Provider component wrapping children
|
|
348
|
+
*
|
|
349
|
+
* @public
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* Connect to an MCP server via tab transport:
|
|
353
|
+
* ```tsx
|
|
354
|
+
* import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
355
|
+
* import { TabClientTransport } from '@mcp-b/transports';
|
|
356
|
+
* import { McpClientProvider } from '@mcp-b/react-webmcp';
|
|
357
|
+
*
|
|
358
|
+
* const client = new Client(
|
|
359
|
+
* { name: 'my-app', version: '1.0.0' },
|
|
360
|
+
* { capabilities: {} }
|
|
361
|
+
* );
|
|
362
|
+
*
|
|
363
|
+
* const transport = new TabClientTransport('mcp', {
|
|
364
|
+
* clientInstanceId: 'my-app-instance',
|
|
365
|
+
* });
|
|
366
|
+
*
|
|
367
|
+
* function App() {
|
|
368
|
+
* return (
|
|
369
|
+
* <McpClientProvider client={client} transport={transport}>
|
|
370
|
+
* <MyAppContent />
|
|
371
|
+
* </McpClientProvider>
|
|
372
|
+
* );
|
|
373
|
+
* }
|
|
374
|
+
* ```
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* Access tools from child components:
|
|
378
|
+
* ```tsx
|
|
379
|
+
* function MyAppContent() {
|
|
380
|
+
* const { tools, isConnected, isLoading } = useMcpClient();
|
|
381
|
+
*
|
|
382
|
+
* if (isLoading) {
|
|
383
|
+
* return <div>Connecting to MCP server...</div>;
|
|
384
|
+
* }
|
|
385
|
+
*
|
|
386
|
+
* if (!isConnected) {
|
|
387
|
+
* return <div>Failed to connect to MCP server</div>;
|
|
388
|
+
* }
|
|
389
|
+
*
|
|
390
|
+
* return (
|
|
391
|
+
* <div>
|
|
392
|
+
* <h2>Available Tools:</h2>
|
|
393
|
+
* <ul>
|
|
394
|
+
* {tools.map(tool => (
|
|
395
|
+
* <li key={tool.name}>{tool.description}</li>
|
|
396
|
+
* ))}
|
|
397
|
+
* </ul>
|
|
398
|
+
* </div>
|
|
399
|
+
* );
|
|
400
|
+
* }
|
|
401
|
+
* ```
|
|
402
|
+
*/
|
|
403
|
+
declare function McpClientProvider({
|
|
404
|
+
children,
|
|
405
|
+
client,
|
|
406
|
+
transport,
|
|
407
|
+
opts
|
|
408
|
+
}: McpClientProviderProps): ReactElement;
|
|
409
|
+
/**
|
|
410
|
+
* Hook to access the MCP client context.
|
|
411
|
+
* Must be used within an {@link McpClientProvider}.
|
|
412
|
+
*
|
|
413
|
+
* @returns The MCP client context including client instance, tools, resources, and connection state
|
|
414
|
+
* @throws Error if used outside of McpClientProvider
|
|
415
|
+
*
|
|
416
|
+
* @public
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* ```tsx
|
|
420
|
+
* function ToolsList() {
|
|
421
|
+
* const { tools, isConnected, error, reconnect } = useMcpClient();
|
|
422
|
+
*
|
|
423
|
+
* if (error) {
|
|
424
|
+
* return (
|
|
425
|
+
* <div>
|
|
426
|
+
* Error: {error.message}
|
|
427
|
+
* <button onClick={reconnect}>Retry</button>
|
|
428
|
+
* </div>
|
|
429
|
+
* );
|
|
430
|
+
* }
|
|
431
|
+
*
|
|
432
|
+
* if (!isConnected) {
|
|
433
|
+
* return <div>Not connected</div>;
|
|
434
|
+
* }
|
|
435
|
+
*
|
|
436
|
+
* return (
|
|
437
|
+
* <ul>
|
|
438
|
+
* {tools.map(tool => (
|
|
439
|
+
* <li key={tool.name}>{tool.description}</li>
|
|
440
|
+
* ))}
|
|
441
|
+
* </ul>
|
|
442
|
+
* );
|
|
443
|
+
* }
|
|
444
|
+
* ```
|
|
445
|
+
*/
|
|
446
|
+
declare function useMcpClient(): McpClientContextValue;
|
|
447
|
+
//#endregion
|
|
448
|
+
export { type CallToolResult, McpClientProvider, type McpClientProviderProps, type ModelContextProtocol, type Resource, type ServerCapabilities, type Tool, type ToolAnnotations, type ToolDescriptor, type ToolExecutionState, type WebMCPConfig, type WebMCPReturn, useMcpClient, useWebMCP, useWebMCPContext };
|
|
449
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/useWebMCP.ts","../src/useWebMCPContext.ts","../src/client/McpClientProvider.tsx"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;AAcA;AAiDiB,UAjDA,kBAiDY,CAAA,UAAA,OAAA,CAAA,CAAA;EACW;;;;EAiCN,WAAA,EAAA,OAAA;EAAjB;;;;EAeI,UAAA,EAvFP,OAuFO,GAAA,IAAA;EAA6C;;;;EAkB3C,KAAA,EAnGd,KAmGc,GAAA,IAAA;EASH;;AAUpB;;EAKS,cAAA,EAAA,MAAA;;;;;;;AClBT;;;;;;;;;;;;;ACvEA;;;;;UFFiB,kCACM,eAAe,CAAA,CAAE,cAAc;;;AG3CvC;;EASN,IAAA,EAAA,MAAA;EACI;;;;EAKa,WAAA,EAAA,MAAA;EAUT;;;;;;AA0FjB;;;;;;EAK4B,WAAA,CAAA,EHlDZ,YGkDY;EAAY;AAgMxC;;;iBH5OiB,eAAe,CAAA,CAAE;;;;;gBAMlB;;;;;;;;mBASG,CAAA,CAAE,MAAM,CAAA,CAAE,UAAU,mBAAmB,QAAQ,WAAW;;;;;;;;0BASnD;;;;;;;;uBASH;;;;;;;;oBASH;;;;;;;;;UAUH;;;;;SAKR,mBAAmB;;;;;;;;;+BAUG,QAAQ;;;;;;;;;;;;;;;;;AAtJvC;AAiDA;;;;;;;;;;;;;;;;;;AAsFA;;;;;;;;;ACbA;;;;;;;;;;;;;ACvEA;;;;;;;;AC5Ce;;;;;;;;AAyBf;;;;AAmBS,iBFuEO,SEvEP,CAAA,qBFwEc,MExEd,CAAA,MAAA,EFwE6B,CAAA,CAAE,UExE/B,CAAA,GFwE6C,MExE7C,CAAA,MAAA,EAAA,KAAA,CAAA,EAAA,UAAA,MAAA,CAAA,CAAA,MAAA,EF0EC,YE1ED,CF0Ec,YE1Ed,EF0E4B,OE1E5B,CAAA,CAAA,EF0EuC,YE1EvC,CF0EoD,OE1EpD,CAAA;;;;;;;;;;;;AHnDT;AAiDA;;;;;;;;;;;;;;;;;;AAsFA;;;;;;;;;ACbA;;;;;;;;;;;;;ACvEA;;;;;;;;AC5Ce;;;AAUF,iBDkCG,gBClCH,CAAA,CAAA,CAAA,CAAA,IAAA,EAAA,MAAA,EAAA,WAAA,EAAA,MAAA,EAAA,QAAA,EAAA,GAAA,GDqCK,CCrCL,CAAA,EDsCV,YCtCU,CDsCG,CCtCH,CAAA;;;;;;;;AHjBb,UGcU,qBAAA,CHdyB;EAiDlB,MAAA,EGlCP,MHkCO;EACuB,KAAA,EGlC/B,MHkC+B,EAAA;EAAjB,SAAA,EGjCV,UHiCU,EAAA;EAA+B,WAAA,EAAA,OAAA;EA2BtC,SAAA,EAAA,OAAA;EAMkB,KAAA,EG/DzB,KH+DyB,GAAA,IAAA;EAAjB,YAAA,EG9DD,oBH8DC,GAAA,IAAA;EAMD,SAAA,EAAA,GAAA,GGnEG,OHmEH,CAAA,IAAA,CAAA;;;;;;;AAkBU,UG3ET,sBAAA,CH2ES;EASH;;;EAmBN,QAAA,EGnGL,SHmGiB;EAKD;;;EAUG,MAAA,EG7GrB,MH6GqB;EAAO;;;aGxGzB;EF4EG;;;EACsC,IAAA,CAAA,EExE7C,cFwE6C;;;;;;;;;;ACxEtD;;;;;;;;AC5Ce;;;;;;;;AAyBf;;;;;;AA0FA;;;;;;;;AAqMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBArMgB,iBAAA;;;;;GAKb,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgMZ,YAAA,CAAA,GAAY"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{createContext as e,useCallback as t,useContext as n,useEffect as r,useRef as i,useState as a}from"react";import{z as o}from"zod";import{ResourceListChangedNotificationSchema as s,ToolListChangedNotificationSchema as c}from"@modelcontextprotocol/sdk/types.js";import{jsx as l}from"react/jsx-runtime";function u(e){let t={},n=[];for(let[r,i]of Object.entries(e)){let e=i.description||void 0,a=`string`;i instanceof o.ZodNumber?a=`number`:i instanceof o.ZodBoolean?a=`boolean`:i instanceof o.ZodArray?a=`array`:i instanceof o.ZodObject&&(a=`object`),t[r]={type:a,...e&&{description:e}},i.isOptional()||n.push(r)}return{type:`object`,properties:t,...n.length>0&&{required:n}}}function d(e){return typeof e==`string`?e:JSON.stringify(e,null,2)}function f(e){let{name:n,description:s,inputSchema:c,outputSchema:l,annotations:f,handler:p,formatOutput:m=d,onSuccess:h,onError:g}=e,[_,v]=a({isExecuting:!1,lastResult:null,error:null,executionCount:0}),y=i(p),b=i(h),x=i(g),S=i(m);r(()=>{y.current=p},[p]),r(()=>{b.current=h},[h]),r(()=>{x.current=g},[g]),r(()=>{S.current=m},[m]);let C=c?o.object(c):null,w=t(async e=>{v(e=>({...e,isExecuting:!0,error:null}));try{let t=C?C.parse(e):e,n=await y.current(t);return v(e=>({isExecuting:!1,lastResult:n,error:null,executionCount:e.executionCount+1})),b.current&&b.current(n,e),n}catch(t){let n=t instanceof Error?t:Error(String(t));throw v(e=>({...e,isExecuting:!1,error:n})),x.current&&x.current(n,e),n}},[C]),T=t(()=>{v({isExecuting:!1,lastResult:null,error:null,executionCount:0})},[]);return r(()=>{if(typeof window>`u`||!window.navigator?.modelContext){console.warn(`[useWebMCP] window.navigator.modelContext is not available. Tool "${n}" will not be registered.`);return}let e=c?u(c):void 0,t=l?u(l):void 0,r=async(e,t)=>{try{let t=await w(e);return{content:[{type:`text`,text:S.current(t)}]}}catch(e){return{content:[{type:`text`,text:`Error: ${e instanceof Error?e.message:String(e)}`}],isError:!0}}},i=window.navigator.modelContext.registerTool({name:n,description:s,inputSchema:e||{type:`object`,properties:{}},...t&&{outputSchema:t},...f&&{annotations:f},execute:async e=>await r(e,{})});if(console.log(`[useWebMCP] Registered tool: ${n}`),typeof window<`u`){let e=window.navigator.modelContext.listTools(),t=window;t.mcpTools=e.map(e=>e.name)}return()=>{if(i&&(i.unregister(),console.log(`[useWebMCP] Unregistered tool: ${n}`),typeof window<`u`&&window.navigator?.modelContext)){let e=window.navigator.modelContext.listTools(),t=window;t.mcpTools=e.map(e=>e.name)}}},[n,s,c,l,f,w]),{state:_,execute:w,reset:T}}function p(e,t,n){let r=i(n);return r.current=n,f({name:e,description:t,annotations:{title:`Context: ${e}`,readOnlyHint:!0,idempotentHint:!0,destructiveHint:!1,openWorldHint:!1},handler:async()=>r.current(),formatOutput:e=>typeof e==`string`?e:JSON.stringify(e,null,2)})}const m=e(null);function h({children:e,client:n,transport:o,opts:u={}}){let[d,f]=a([]),[p,h]=a([]),[g,_]=a(!1),[v,y]=a(null),[b,x]=a(!1),[S,C]=a(null),w=i(`disconnected`),T=t(async()=>{if(n){if(!n.getServerCapabilities()?.resources){f([]);return}try{f((await n.listResources()).resources)}catch(e){throw console.error(`Error fetching resources:`,e),e}}},[n]),E=t(async()=>{if(n){if(!n.getServerCapabilities()?.tools){h([]);return}try{h((await n.listTools()).tools)}catch(e){throw console.error(`Error fetching tools:`,e),e}}},[n]),D=t(async()=>{if(!n||!o)throw Error(`Client or transport not available`);if(w.current===`disconnected`){w.current=`connecting`,_(!0),y(null);try{await n.connect(o,u);let e=n.getServerCapabilities();x(!0),C(e||null),w.current=`connected`,await Promise.all([T(),E()])}catch(e){let t=e instanceof Error?e:Error(String(e));throw w.current=`disconnected`,y(t),t}finally{_(!1)}}},[n,o,u,T,E]);return r(()=>{if(!b||!n)return;let e=n.getServerCapabilities();return e?.resources?.listChanged&&n.setNotificationHandler(s,()=>{T().catch(console.error)}),e?.tools?.listChanged&&n.setNotificationHandler(c,()=>{E().catch(console.error)}),()=>{e?.resources?.listChanged&&n.removeNotificationHandler(`notifications/resources/list_changed`),e?.tools?.listChanged&&n.removeNotificationHandler(`notifications/tools/list_changed`)}},[n,b,T,E]),r(()=>(D().catch(e=>{console.error(`Failed to connect MCP client:`,e)}),()=>{w.current=`disconnected`,x(!1)}),[n,o]),l(m.Provider,{value:{client:n,tools:p,resources:d,isConnected:b,isLoading:g,error:v,capabilities:S,reconnect:D},children:e})}function g(){let e=n(m);if(!e)throw Error(`useMcpClient must be used within an McpClientProvider`);return e}export{h as McpClientProvider,g as useMcpClient,f as useWebMCP,p as useWebMCPContext};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["properties: Record<string, unknown>","required: string[]"],"sources":["../src/useWebMCP.ts","../src/useWebMCPContext.ts","../src/client/McpClientProvider.tsx"],"sourcesContent":["import type { InputSchema } from '@mcp-b/global';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { z } from 'zod';\nimport type { ToolExecutionState, WebMCPConfig, WebMCPReturn } from './types.js';\n\n/**\n * Converts a Zod schema object to JSON Schema format for MCP.\n * Handles basic type inference for common Zod types.\n *\n * @internal\n * @param schema - Record of Zod type definitions\n * @returns JSON Schema object with type, properties, and required fields\n */\nfunction zodToJsonSchema(schema: Record<string, z.ZodTypeAny>): {\n type: string;\n properties?: Record<string, unknown>;\n required?: string[];\n} {\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const [key, zodType] of Object.entries(schema)) {\n const description = (zodType as { description?: string }).description || undefined;\n\n let type = 'string';\n if (zodType instanceof z.ZodNumber) {\n type = 'number';\n } else if (zodType instanceof z.ZodBoolean) {\n type = 'boolean';\n } else if (zodType instanceof z.ZodArray) {\n type = 'array';\n } else if (zodType instanceof z.ZodObject) {\n type = 'object';\n }\n\n properties[key] = {\n type,\n ...(description && { description }),\n };\n\n if (!zodType.isOptional()) {\n required.push(key);\n }\n }\n\n return {\n type: 'object',\n properties,\n ...(required.length > 0 && { required }),\n };\n}\n\n/**\n * Default output formatter that converts values to formatted JSON strings.\n *\n * @internal\n * @param output - The value to format\n * @returns Formatted string representation\n */\nfunction defaultFormatOutput(output: unknown): string {\n if (typeof output === 'string') {\n return output;\n }\n return JSON.stringify(output, null, 2);\n}\n\n/**\n * React hook for registering and managing Model Context Protocol (MCP) tools.\n *\n * This hook handles the complete lifecycle of an MCP tool:\n * - Registers the tool with `window.navigator.modelContext`\n * - Manages execution state (loading, results, errors)\n * - Validates input using Zod schemas\n * - Handles tool execution and lifecycle callbacks\n * - Automatically unregisters on component unmount\n *\n * @template TInputSchema - Zod schema object defining input parameter types\n * @template TOutput - Type of data returned by the handler function\n *\n * @param config - Configuration object for the tool\n * @returns Object containing execution state and control methods\n *\n * @public\n *\n * @example\n * Basic tool registration:\n * ```tsx\n * function PostActions() {\n * const likeTool = useWebMCP({\n * name: 'posts_like',\n * description: 'Like a post by ID',\n * inputSchema: {\n * postId: z.string().uuid().describe('The ID of the post to like'),\n * },\n * handler: async ({ postId }) => {\n * await api.posts.like(postId);\n * return { success: true, postId };\n * },\n * });\n *\n * if (likeTool.state.isExecuting) {\n * return <Spinner />;\n * }\n *\n * return <div>Post actions ready</div>;\n * }\n * ```\n *\n * @example\n * Tool with annotations and callbacks:\n * ```tsx\n * const deleteTool = useWebMCP({\n * name: 'posts_delete',\n * description: 'Delete a post permanently',\n * inputSchema: {\n * postId: z.string().uuid(),\n * },\n * annotations: {\n * destructiveHint: true,\n * idempotentHint: false,\n * },\n * handler: async ({ postId }) => {\n * await api.posts.delete(postId);\n * return { deleted: true };\n * },\n * onSuccess: () => {\n * navigate('/posts');\n * toast.success('Post deleted');\n * },\n * onError: (error) => {\n * toast.error(`Failed to delete: ${error.message}`);\n * },\n * });\n * ```\n */\nexport function useWebMCP<\n TInputSchema extends Record<string, z.ZodTypeAny> = Record<string, never>,\n TOutput = string,\n>(config: WebMCPConfig<TInputSchema, TOutput>): WebMCPReturn<TOutput> {\n const {\n name,\n description,\n inputSchema,\n outputSchema,\n annotations,\n handler,\n formatOutput = defaultFormatOutput,\n onSuccess,\n onError,\n } = config;\n\n const [state, setState] = useState<ToolExecutionState<TOutput>>({\n isExecuting: false,\n lastResult: null,\n error: null,\n executionCount: 0,\n });\n\n const handlerRef = useRef(handler);\n const onSuccessRef = useRef(onSuccess);\n const onErrorRef = useRef(onError);\n const formatOutputRef = useRef(formatOutput);\n\n useEffect(() => {\n handlerRef.current = handler;\n }, [handler]);\n\n useEffect(() => {\n onSuccessRef.current = onSuccess;\n }, [onSuccess]);\n\n useEffect(() => {\n onErrorRef.current = onError;\n }, [onError]);\n\n useEffect(() => {\n formatOutputRef.current = formatOutput;\n }, [formatOutput]);\n\n const validator = inputSchema ? z.object(inputSchema) : null;\n\n /**\n * Executes the tool handler with input validation and state management.\n *\n * @param input - The input parameters to validate and pass to the handler\n * @returns Promise resolving to the handler's output\n * @throws Error if validation fails or the handler throws\n */\n const execute = useCallback(\n async (input: unknown): Promise<TOutput> => {\n setState((prev) => ({\n ...prev,\n isExecuting: true,\n error: null,\n }));\n\n try {\n const validatedInput = validator ? validator.parse(input) : input;\n const result = await handlerRef.current(validatedInput as never);\n\n setState((prev) => ({\n isExecuting: false,\n lastResult: result,\n error: null,\n executionCount: prev.executionCount + 1,\n }));\n\n if (onSuccessRef.current) {\n onSuccessRef.current(result, input);\n }\n\n return result;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n setState((prev) => ({\n ...prev,\n isExecuting: false,\n error: err,\n }));\n\n if (onErrorRef.current) {\n onErrorRef.current(err, input);\n }\n\n throw err;\n }\n },\n [validator]\n );\n\n /**\n * Resets the execution state to initial values.\n */\n const reset = useCallback(() => {\n setState({\n isExecuting: false,\n lastResult: null,\n error: null,\n executionCount: 0,\n });\n }, []);\n\n useEffect(() => {\n if (typeof window === 'undefined' || !window.navigator?.modelContext) {\n console.warn(\n `[useWebMCP] window.navigator.modelContext is not available. Tool \"${name}\" will not be registered.`\n );\n return;\n }\n\n const inputJsonSchema = inputSchema ? zodToJsonSchema(inputSchema) : undefined;\n const outputJsonSchema = outputSchema ? zodToJsonSchema(outputSchema) : undefined;\n\n const mcpHandler = async (input: unknown, _extra: unknown): Promise<CallToolResult> => {\n try {\n const result = await execute(input);\n const formattedOutput = formatOutputRef.current(result);\n\n return {\n content: [\n {\n type: 'text',\n text: formattedOutput,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n };\n\n const fallbackInputSchema: InputSchema = {\n type: 'object',\n properties: {},\n };\n\n const registration = window.navigator.modelContext.registerTool({\n name,\n description,\n inputSchema: (inputJsonSchema || fallbackInputSchema) as InputSchema,\n ...(outputJsonSchema && { outputSchema: outputJsonSchema as InputSchema }),\n ...(annotations && { annotations }),\n execute: async (args: Record<string, unknown>) => {\n const result = await mcpHandler(args, {});\n return result;\n },\n });\n\n console.log(`[useWebMCP] Registered tool: ${name}`);\n\n // Expose registered tools on window for testing\n if (typeof window !== 'undefined') {\n const tools = window.navigator.modelContext.listTools();\n const w = window as unknown as Window & { mcpTools: string[] };\n w.mcpTools = tools.map((t) => t.name);\n }\n\n return () => {\n if (registration) {\n registration.unregister();\n console.log(`[useWebMCP] Unregistered tool: ${name}`);\n\n // Update window.mcpTools after unregistration\n if (typeof window !== 'undefined' && window.navigator?.modelContext) {\n const tools = window.navigator.modelContext.listTools();\n const w = window as unknown as Window & { mcpTools: string[] };\n w.mcpTools = tools.map((t) => t.name);\n }\n }\n };\n }, [name, description, inputSchema, outputSchema, annotations, execute]);\n\n return {\n state,\n execute,\n reset,\n };\n}\n","import { useRef } from 'react';\nimport type { 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 * @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<T> {\n const getValueRef = useRef(getValue);\n getValueRef.current = getValue;\n\n return useWebMCP<Record<string, never>, T>({\n name,\n description,\n annotations: {\n title: `Context: ${name}`,\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: false,\n },\n handler: async () => {\n return getValueRef.current();\n },\n formatOutput: (output) => {\n if (typeof output === 'string') {\n return output;\n }\n return JSON.stringify(output, null, 2);\n },\n });\n}\n","import type { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport type { RequestOptions } from '@modelcontextprotocol/sdk/shared/protocol.js';\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport type {\n Tool as McpTool,\n Resource,\n ServerCapabilities,\n} from '@modelcontextprotocol/sdk/types.js';\nimport {\n ResourceListChangedNotificationSchema,\n ToolListChangedNotificationSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport {\n createContext,\n type ReactElement,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from 'react';\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: () => Promise<void>;\n}\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 request options for the connection.\n */\n opts?: RequestOptions;\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 { Client } from '@modelcontextprotocol/sdk/client/index.js';\n * import { TabClientTransport } from '@mcp-b/transports';\n * import { McpClientProvider } from '@mcp-b/react-webmcp';\n *\n * const client = new Client(\n * { name: 'my-app', version: '1.0.0' },\n * { capabilities: {} }\n * );\n *\n * const transport = new TabClientTransport('mcp', {\n * clientInstanceId: 'my-app-instance',\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 [isLoading, setIsLoading] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n const [isConnected, setIsConnected] = useState<boolean>(false);\n const [capabilities, setCapabilities] = useState<ServerCapabilities | null>(null);\n\n const connectionStateRef = useRef<'disconnected' | 'connecting' | 'connected'>('disconnected');\n\n /**\n * Fetches available resources from the MCP server.\n * Only fetches if the server supports the resources capability.\n */\n const fetchResourcesInternal = useCallback(async () => {\n if (!client) return;\n\n const serverCapabilities = client.getServerCapabilities();\n if (!serverCapabilities?.resources) {\n setResources([]);\n return;\n }\n\n try {\n const response = await client.listResources();\n setResources(response.resources);\n } catch (e) {\n console.error('Error fetching resources:', e);\n throw e;\n }\n }, [client]);\n\n /**\n * Fetches available tools from the MCP server.\n * Only fetches if the server supports the tools capability.\n */\n const fetchToolsInternal = useCallback(async () => {\n if (!client) return;\n\n const serverCapabilities = client.getServerCapabilities();\n if (!serverCapabilities?.tools) {\n setTools([]);\n return;\n }\n\n try {\n const response = await client.listTools();\n setTools(response.tools);\n } catch (e) {\n console.error('Error fetching tools:', e);\n throw e;\n }\n }, [client]);\n\n /**\n * Establishes connection to the MCP server.\n * Safe to call multiple times - will no-op if already connected or connecting.\n */\n const reconnect = useCallback(async () => {\n if (!client || !transport) {\n throw new Error('Client or transport not available');\n }\n\n if (connectionStateRef.current !== 'disconnected') {\n return;\n }\n\n connectionStateRef.current = 'connecting';\n setIsLoading(true);\n setError(null);\n\n try {\n await client.connect(transport, opts);\n const caps = client.getServerCapabilities();\n setIsConnected(true);\n setCapabilities(caps || null);\n connectionStateRef.current = 'connected';\n\n await Promise.all([fetchResourcesInternal(), fetchToolsInternal()]);\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n connectionStateRef.current = 'disconnected';\n setError(err);\n throw err;\n } finally {\n setIsLoading(false);\n }\n }, [client, transport, opts, fetchResourcesInternal, fetchToolsInternal]);\n\n useEffect(() => {\n if (!isConnected || !client) {\n return;\n }\n\n const serverCapabilities = client.getServerCapabilities();\n\n const handleResourcesChanged = () => {\n fetchResourcesInternal().catch(console.error);\n };\n\n const handleToolsChanged = () => {\n fetchToolsInternal().catch(console.error);\n };\n\n if (serverCapabilities?.resources?.listChanged) {\n client.setNotificationHandler(ResourceListChangedNotificationSchema, handleResourcesChanged);\n }\n\n if (serverCapabilities?.tools?.listChanged) {\n client.setNotificationHandler(ToolListChangedNotificationSchema, handleToolsChanged);\n }\n\n return () => {\n if (serverCapabilities?.resources?.listChanged) {\n client.removeNotificationHandler('notifications/resources/list_changed');\n }\n\n if (serverCapabilities?.tools?.listChanged) {\n client.removeNotificationHandler('notifications/tools/list_changed');\n }\n };\n }, [client, isConnected, fetchResourcesInternal, fetchToolsInternal]);\n\n useEffect(() => {\n // Initial connection - reconnect() has its own guard to prevent concurrent connections\n reconnect().catch((err) => {\n console.error('Failed to connect MCP client:', err);\n });\n\n // Cleanup: mark as disconnected so next mount will reconnect\n return () => {\n connectionStateRef.current = 'disconnected';\n setIsConnected(false);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [client, transport]);\n\n return (\n <McpClientContext.Provider\n value={{\n client,\n tools,\n resources,\n isConnected,\n isLoading,\n error,\n capabilities,\n reconnect,\n }}\n >\n {children}\n </McpClientContext.Provider>\n );\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={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"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mcp-b/react-webmcp",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "React hooks for Model Context Protocol - register tools via navigator.modelContext and consume tools from MCP servers",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"model-context-protocol",
|
|
8
|
+
"react",
|
|
9
|
+
"hooks",
|
|
10
|
+
"react-hooks",
|
|
11
|
+
"browser",
|
|
12
|
+
"ai",
|
|
13
|
+
"assistant",
|
|
14
|
+
"tools",
|
|
15
|
+
"zod"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/WebMCP-org/WebMCP#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/WebMCP-org/WebMCP/issues"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/WebMCP-org/WebMCP.git",
|
|
24
|
+
"directory": "packages/react-webmcp"
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"author": "WebMCP Team",
|
|
28
|
+
"type": "module",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"import": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"main": "./dist/index.js",
|
|
36
|
+
"files": [
|
|
37
|
+
"dist"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@modelcontextprotocol/sdk": "1.15.0",
|
|
41
|
+
"@mcp-b/global": "1.0.14",
|
|
42
|
+
"@mcp-b/webmcp-ts-sdk": "1.0.1",
|
|
43
|
+
"@mcp-b/transports": "1.0.3"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^22.15.21",
|
|
47
|
+
"@types/react": "^19.1.2",
|
|
48
|
+
"tsdown": "^0.15.10",
|
|
49
|
+
"typescript": "^5.8.3",
|
|
50
|
+
"zod": "^3.25.76"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"react": "^19.1.0",
|
|
54
|
+
"zod": "^3.25.76"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public",
|
|
58
|
+
"registry": "https://registry.npmjs.org/"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsdown",
|
|
62
|
+
"build:prod": "NODE_ENV=prod tsdown",
|
|
63
|
+
"check": "biome check --write .",
|
|
64
|
+
"clean": "rm -rf dist .turbo",
|
|
65
|
+
"format": "biome format --write .",
|
|
66
|
+
"lint": "biome lint --write .",
|
|
67
|
+
"publish:dry": "pnpm publish --access public --dry-run",
|
|
68
|
+
"publish:npm": "pnpm publish --access public",
|
|
69
|
+
"typecheck": "tsc --noEmit",
|
|
70
|
+
"version:major": "pnpm version major --no-git-tag-version",
|
|
71
|
+
"version:minor": "pnpm version minor --no-git-tag-version",
|
|
72
|
+
"version:patch": "pnpm version patch --no-git-tag-version"
|
|
73
|
+
}
|
|
74
|
+
}
|