usewebmcp 0.3.0 → 0.3.2
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 +37 -10
- package/dist/index.d.ts +62 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -7,6 +7,14 @@ Standalone React hooks for strict core WebMCP tool registration via `navigator.m
|
|
|
7
7
|
- Use `usewebmcp` for strict core WebMCP workflows.
|
|
8
8
|
- Use `@mcp-b/react-webmcp` for full MCP-B runtime features (resources, prompts, client/provider flows, etc.).
|
|
9
9
|
|
|
10
|
+
## Type Safety First
|
|
11
|
+
|
|
12
|
+
`usewebmcp` is built for schema-driven types:
|
|
13
|
+
|
|
14
|
+
- `config.execute`/`config.handler` input is inferred from `inputSchema`
|
|
15
|
+
- Tool result type is inferred from `outputSchema`
|
|
16
|
+
- `state.lastResult` and returned `execute(input)` are typed from that same inferred output
|
|
17
|
+
|
|
10
18
|
## Package Selection
|
|
11
19
|
|
|
12
20
|
| Package | Use When |
|
|
@@ -70,7 +78,7 @@ export function CounterTool() {
|
|
|
70
78
|
description: 'Get current count',
|
|
71
79
|
inputSchema: COUNTER_INPUT_SCHEMA,
|
|
72
80
|
outputSchema: COUNTER_OUTPUT_SCHEMA,
|
|
73
|
-
|
|
81
|
+
execute: async () => ({ count: 42 }),
|
|
74
82
|
});
|
|
75
83
|
|
|
76
84
|
return (
|
|
@@ -78,6 +86,13 @@ export function CounterTool() {
|
|
|
78
86
|
<p>Executions: {counterTool.state.executionCount}</p>
|
|
79
87
|
<p>Last count: {counterTool.state.lastResult?.count ?? 'none'}</p>
|
|
80
88
|
{counterTool.state.error && <p>Error: {counterTool.state.error.message}</p>}
|
|
89
|
+
<button
|
|
90
|
+
onClick={async () => {
|
|
91
|
+
await counterTool.execute({});
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
Run Tool Locally
|
|
95
|
+
</button>
|
|
81
96
|
</div>
|
|
82
97
|
);
|
|
83
98
|
}
|
|
@@ -94,7 +109,17 @@ export function CounterTool() {
|
|
|
94
109
|
- `state.executionCount`
|
|
95
110
|
- Returns `execute(input)` for manual in-app invocation and `reset()` for state reset.
|
|
96
111
|
|
|
97
|
-
`handler` can be synchronous or asynchronous.
|
|
112
|
+
Your tool implementation (`config.execute` or `config.handler`) can be synchronous or asynchronous.
|
|
113
|
+
|
|
114
|
+
## `config.execute` vs returned `execute(...)`
|
|
115
|
+
|
|
116
|
+
- `config.execute`: preferred config field for tool logic.
|
|
117
|
+
- `config.handler`: backward-compatible alias for `config.execute`.
|
|
118
|
+
- returned `execute(input)`: hook return function for local manual invocation from your UI/tests.
|
|
119
|
+
|
|
120
|
+
If both `config.execute` and `config.handler` are provided, `config.execute` is used.
|
|
121
|
+
|
|
122
|
+
Both paths run the same underlying tool logic and update the hook state.
|
|
98
123
|
|
|
99
124
|
## Type Inference
|
|
100
125
|
|
|
@@ -120,7 +145,7 @@ useWebMCP({
|
|
|
120
145
|
name: 'search',
|
|
121
146
|
description: 'Search docs',
|
|
122
147
|
inputSchema: INPUT_SCHEMA,
|
|
123
|
-
|
|
148
|
+
execute(input) {
|
|
124
149
|
// input is inferred as { query: string; limit?: number }
|
|
125
150
|
return { total: 1 };
|
|
126
151
|
},
|
|
@@ -131,7 +156,7 @@ useWebMCP({
|
|
|
131
156
|
|
|
132
157
|
When `outputSchema` is provided as a literal JSON object schema:
|
|
133
158
|
|
|
134
|
-
-
|
|
159
|
+
- implementation return type is inferred from `outputSchema`
|
|
135
160
|
- `state.lastResult` is inferred to the same type
|
|
136
161
|
- MCP response includes `structuredContent`
|
|
137
162
|
|
|
@@ -149,7 +174,7 @@ const tool = useWebMCP({
|
|
|
149
174
|
name: 'count_items',
|
|
150
175
|
description: 'Count items',
|
|
151
176
|
outputSchema: OUTPUT_SCHEMA,
|
|
152
|
-
|
|
177
|
+
execute: () => ({ total: 3 }),
|
|
153
178
|
});
|
|
154
179
|
|
|
155
180
|
// tool.state.lastResult is inferred as { total: number } | null
|
|
@@ -170,7 +195,7 @@ function SearchToolPanel() {
|
|
|
170
195
|
required: ['query'],
|
|
171
196
|
additionalProperties: false,
|
|
172
197
|
} as const,
|
|
173
|
-
|
|
198
|
+
execute: async ({ query }) => ({ query, total: query.length }),
|
|
174
199
|
});
|
|
175
200
|
|
|
176
201
|
return (
|
|
@@ -187,7 +212,7 @@ function SearchToolPanel() {
|
|
|
187
212
|
|
|
188
213
|
## Output Schema Contract
|
|
189
214
|
|
|
190
|
-
If `outputSchema` is defined,
|
|
215
|
+
If `outputSchema` is defined, your tool implementation must return a JSON-serializable object result.
|
|
191
216
|
|
|
192
217
|
Returning a non-object value (`string`, `null`, array, etc.) causes an error response from the registered MCP tool.
|
|
193
218
|
|
|
@@ -204,6 +229,7 @@ The tool re-registers when any of these change:
|
|
|
204
229
|
|
|
205
230
|
The hook avoids re-registration when only callback references change:
|
|
206
231
|
|
|
232
|
+
- `execute`
|
|
207
233
|
- `handler`
|
|
208
234
|
- `onSuccess`
|
|
209
235
|
- `onError`
|
|
@@ -227,7 +253,8 @@ Recommendation:
|
|
|
227
253
|
- `inputSchema?`
|
|
228
254
|
- `outputSchema?`
|
|
229
255
|
- `annotations?`
|
|
230
|
-
- `
|
|
256
|
+
- `execute(input)` (preferred)
|
|
257
|
+
- `handler(input)` (backward-compatible alias)
|
|
231
258
|
- `formatOutput?(output)` (deprecated)
|
|
232
259
|
- `onSuccess?(result, input)`
|
|
233
260
|
- `onError?(error, input)`
|
|
@@ -238,14 +265,14 @@ Return value:
|
|
|
238
265
|
- `execute(input)`
|
|
239
266
|
- `reset()`
|
|
240
267
|
|
|
241
|
-
`execute(input)` is a local direct call to your
|
|
268
|
+
`execute(input)` is a local direct call to your configured tool implementation for in-app control/testing.
|
|
242
269
|
Tool calls coming from MCP clients still go through `navigator.modelContext`.
|
|
243
270
|
|
|
244
271
|
## Important Notes
|
|
245
272
|
|
|
246
273
|
- This is a client-side hook package (`'use client'`).
|
|
247
274
|
- `formatOutput` is deprecated; prefer `outputSchema` + structured output.
|
|
248
|
-
- When
|
|
275
|
+
- When tool output is not a string, default text content is pretty-printed JSON.
|
|
249
276
|
|
|
250
277
|
## License
|
|
251
278
|
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { InferArgsFromInputSchema, InferJsonSchema, InputSchema, JsonSchemaObjec
|
|
|
5
5
|
//#region src/types.d.ts
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Infers
|
|
8
|
+
* Infers tool input type from either a Standard Schema or JSON Schema.
|
|
9
9
|
*
|
|
10
10
|
* - **Standard Schema** (Zod v4, Valibot, ArkType): extracts `~standard.types.input`
|
|
11
11
|
* - **JSON Schema** (`as const`): uses `InferArgsFromInputSchema` for structural inference
|
|
@@ -37,7 +37,7 @@ type InferOutput<TOutputSchema extends JsonSchemaObject | undefined = undefined,
|
|
|
37
37
|
* Represents the current execution state of a tool, including loading status,
|
|
38
38
|
* results, errors, and execution history.
|
|
39
39
|
*
|
|
40
|
-
* @template TOutput - The type of data returned by the tool
|
|
40
|
+
* @template TOutput - The type of data returned by the tool implementation
|
|
41
41
|
* @public
|
|
42
42
|
*/
|
|
43
43
|
interface ToolExecutionState<TOutput = unknown> {
|
|
@@ -63,9 +63,20 @@ interface ToolExecutionState<TOutput = unknown> {
|
|
|
63
63
|
executionCount: number;
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
66
|
+
* Tool implementation function used by `useWebMCP`.
|
|
67
|
+
*
|
|
68
|
+
* Supports sync or async implementations.
|
|
69
|
+
*
|
|
70
|
+
* @template TInputSchema - Schema defining input parameters
|
|
71
|
+
* @template TOutputSchema - Optional JSON Schema object defining output structure
|
|
72
|
+
*
|
|
73
|
+
* @public
|
|
74
|
+
*/
|
|
75
|
+
type ToolExecuteFunction<TInputSchema extends ToolInputSchema = InputSchema, TOutputSchema extends JsonSchemaObject | undefined = undefined> = (input: InferToolInput<TInputSchema>) => Promise<InferOutput<TOutputSchema>> | InferOutput<TOutputSchema>;
|
|
76
|
+
/**
|
|
77
|
+
* Shared configuration fields for the `useWebMCP` hook.
|
|
67
78
|
*
|
|
68
|
-
* Defines a tool's metadata, schema,
|
|
79
|
+
* Defines a tool's metadata, schema, and lifecycle callbacks.
|
|
69
80
|
* Uses JSON Schema for type inference via `as const`.
|
|
70
81
|
*
|
|
71
82
|
* @template TInputSchema - JSON Schema defining input parameters
|
|
@@ -83,7 +94,7 @@ interface ToolExecutionState<TOutput = unknown> {
|
|
|
83
94
|
* properties: { postId: { type: 'string' } },
|
|
84
95
|
* required: ['postId'],
|
|
85
96
|
* } as const,
|
|
86
|
-
*
|
|
97
|
+
* execute: async ({ postId }) => {
|
|
87
98
|
* await api.likePost(postId);
|
|
88
99
|
* return { success: true };
|
|
89
100
|
* },
|
|
@@ -107,14 +118,14 @@ interface ToolExecutionState<TOutput = unknown> {
|
|
|
107
118
|
* likedAt: { type: 'string', description: 'ISO timestamp of the like' },
|
|
108
119
|
* },
|
|
109
120
|
* } as const,
|
|
110
|
-
*
|
|
121
|
+
* execute: async ({ postId }) => {
|
|
111
122
|
* const result = await api.likePost(postId);
|
|
112
123
|
* return { likes: result.likes, likedAt: new Date().toISOString() };
|
|
113
124
|
* },
|
|
114
125
|
* });
|
|
115
126
|
* ```
|
|
116
127
|
*/
|
|
117
|
-
interface
|
|
128
|
+
interface WebMCPConfigBase<TInputSchema extends ToolInputSchema = InputSchema, TOutputSchema extends JsonSchemaObject | undefined = undefined> {
|
|
118
129
|
/**
|
|
119
130
|
* Unique identifier for the tool (e.g., 'posts_like', 'graph_navigate').
|
|
120
131
|
* Must follow naming conventions: lowercase with underscores.
|
|
@@ -151,7 +162,7 @@ interface WebMCPConfig<TInputSchema extends ToolInputSchema = InputSchema, TOutp
|
|
|
151
162
|
* **Recommended:** JSON Schema object defining the expected output structure.
|
|
152
163
|
*
|
|
153
164
|
* When provided, this enables three key features:
|
|
154
|
-
* 1. **Type Safety**: The
|
|
165
|
+
* 1. **Type Safety**: The implementation return type is inferred from this schema
|
|
155
166
|
* 2. **MCP structuredContent**: The MCP response includes `structuredContent`
|
|
156
167
|
* containing the typed output per the MCP specification
|
|
157
168
|
* 3. **AI Understanding**: AI models can better understand and use the tool's output
|
|
@@ -175,17 +186,6 @@ interface WebMCPConfig<TInputSchema extends ToolInputSchema = InputSchema, TOutp
|
|
|
175
186
|
* See {@link ToolAnnotations} for available options.
|
|
176
187
|
*/
|
|
177
188
|
annotations?: ToolAnnotations;
|
|
178
|
-
/**
|
|
179
|
-
* The function that executes when the tool is called.
|
|
180
|
-
* Can be synchronous or asynchronous.
|
|
181
|
-
*
|
|
182
|
-
* When `outputSchema` is provided, the return type is inferred from the schema.
|
|
183
|
-
* Otherwise, any return type is allowed.
|
|
184
|
-
*
|
|
185
|
-
* @param input - Validated input parameters matching the inputSchema
|
|
186
|
-
* @returns The result data or a Promise resolving to the result
|
|
187
|
-
*/
|
|
188
|
-
handler: (input: InferToolInput<TInputSchema>) => Promise<InferOutput<TOutputSchema>> | InferOutput<TOutputSchema>;
|
|
189
189
|
/**
|
|
190
190
|
* Custom formatter for the MCP text response.
|
|
191
191
|
*
|
|
@@ -193,7 +193,7 @@ interface WebMCPConfig<TInputSchema extends ToolInputSchema = InputSchema, TOutp
|
|
|
193
193
|
* structured output via MCP's `structuredContent`, which is the recommended
|
|
194
194
|
* approach for tool outputs. This property will be removed in a future version.
|
|
195
195
|
*
|
|
196
|
-
* @param output - The raw output from the
|
|
196
|
+
* @param output - The raw output from the tool implementation
|
|
197
197
|
* @returns Formatted string for the MCP response content
|
|
198
198
|
*/
|
|
199
199
|
formatOutput?: (output: InferOutput<TOutputSchema>) => string;
|
|
@@ -201,8 +201,8 @@ interface WebMCPConfig<TInputSchema extends ToolInputSchema = InputSchema, TOutp
|
|
|
201
201
|
* Optional callback invoked when the tool execution succeeds.
|
|
202
202
|
* Useful for triggering side effects like navigation or analytics.
|
|
203
203
|
*
|
|
204
|
-
* @param result - The successful result from the
|
|
205
|
-
* @param input - The input that was passed to the
|
|
204
|
+
* @param result - The successful result from the tool implementation
|
|
205
|
+
* @param input - The input that was passed to the tool implementation
|
|
206
206
|
*/
|
|
207
207
|
onSuccess?: (result: InferOutput<TOutputSchema>, input: unknown) => void;
|
|
208
208
|
/**
|
|
@@ -210,10 +210,44 @@ interface WebMCPConfig<TInputSchema extends ToolInputSchema = InputSchema, TOutp
|
|
|
210
210
|
* Useful for error handling, logging, or showing user notifications.
|
|
211
211
|
*
|
|
212
212
|
* @param error - The error that occurred during execution
|
|
213
|
-
* @param input - The input that was passed to the
|
|
213
|
+
* @param input - The input that was passed to the tool implementation
|
|
214
214
|
*/
|
|
215
215
|
onError?: (error: Error, input: unknown) => void;
|
|
216
216
|
}
|
|
217
|
+
type WebMCPConfigImplementation<TInputSchema extends ToolInputSchema = InputSchema, TOutputSchema extends JsonSchemaObject | undefined = undefined> = {
|
|
218
|
+
/**
|
|
219
|
+
* Preferred tool implementation function.
|
|
220
|
+
*/
|
|
221
|
+
execute: ToolExecuteFunction<TInputSchema, TOutputSchema>;
|
|
222
|
+
/**
|
|
223
|
+
* Backward-compatible alias for `execute`.
|
|
224
|
+
*/
|
|
225
|
+
handler?: ToolExecuteFunction<TInputSchema, TOutputSchema>;
|
|
226
|
+
} | {
|
|
227
|
+
/**
|
|
228
|
+
* Backward-compatible alias for `execute`.
|
|
229
|
+
*/
|
|
230
|
+
handler: ToolExecuteFunction<TInputSchema, TOutputSchema>;
|
|
231
|
+
/**
|
|
232
|
+
* Preferred tool implementation function.
|
|
233
|
+
*/
|
|
234
|
+
execute?: ToolExecuteFunction<TInputSchema, TOutputSchema>;
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* Configuration options for the `useWebMCP` hook.
|
|
238
|
+
*
|
|
239
|
+
* You can provide tool logic with either:
|
|
240
|
+
* - `execute` (preferred), or
|
|
241
|
+
* - `handler` (backward-compatible alias).
|
|
242
|
+
*
|
|
243
|
+
* If both are provided, `execute` is used.
|
|
244
|
+
*
|
|
245
|
+
* @template TInputSchema - JSON Schema defining input parameters
|
|
246
|
+
* @template TOutputSchema - JSON Schema object defining output structure (enables structuredContent)
|
|
247
|
+
*
|
|
248
|
+
* @public
|
|
249
|
+
*/
|
|
250
|
+
type WebMCPConfig<TInputSchema extends ToolInputSchema = InputSchema, TOutputSchema extends JsonSchemaObject | undefined = undefined> = WebMCPConfigBase<TInputSchema, TOutputSchema> & WebMCPConfigImplementation<TInputSchema, TOutputSchema>;
|
|
217
251
|
/**
|
|
218
252
|
* Return value from the `useWebMCP` hook.
|
|
219
253
|
* Provides access to execution state and methods for manual tool control.
|
|
@@ -233,7 +267,7 @@ interface WebMCPReturn<TOutputSchema extends JsonSchemaObject | undefined = unde
|
|
|
233
267
|
*
|
|
234
268
|
* @param input - The input parameters to pass to the tool
|
|
235
269
|
* @returns Promise resolving to the tool's output
|
|
236
|
-
* @throws Error if validation fails or
|
|
270
|
+
* @throws Error if validation fails or tool implementation throws
|
|
237
271
|
*/
|
|
238
272
|
execute: (input: unknown) => Promise<InferOutput<TOutputSchema>>;
|
|
239
273
|
/**
|
|
@@ -278,7 +312,7 @@ interface WebMCPReturn<TOutputSchema extends JsonSchemaObject | undefined = unde
|
|
|
278
312
|
* email: { type: 'string' },
|
|
279
313
|
* },
|
|
280
314
|
* } as const,
|
|
281
|
-
*
|
|
315
|
+
* execute: async ({ userId }) => {
|
|
282
316
|
* const user = await fetchUser(userId);
|
|
283
317
|
* return { id: user.id, name: user.name, email: user.email };
|
|
284
318
|
* },
|
|
@@ -289,7 +323,7 @@ interface WebMCPReturn<TOutputSchema extends JsonSchemaObject | undefined = unde
|
|
|
289
323
|
*
|
|
290
324
|
* This hook is optimized to minimize unnecessary tool re-registrations:
|
|
291
325
|
*
|
|
292
|
-
* - **Ref-based callbacks**: `handler`, `onSuccess`, `onError`, and `formatOutput`
|
|
326
|
+
* - **Ref-based callbacks**: `execute`/`handler`, `onSuccess`, `onError`, and `formatOutput`
|
|
293
327
|
* are stored in refs, so changing these functions won't trigger re-registration.
|
|
294
328
|
*
|
|
295
329
|
* **IMPORTANT**: If `inputSchema`, `outputSchema`, or `annotations` are defined inline
|
|
@@ -338,7 +372,7 @@ interface WebMCPReturn<TOutputSchema extends JsonSchemaObject | undefined = unde
|
|
|
338
372
|
* likeCount: { type: 'number' },
|
|
339
373
|
* },
|
|
340
374
|
* } as const,
|
|
341
|
-
*
|
|
375
|
+
* execute: async ({ postId }) => {
|
|
342
376
|
* const result = await api.posts.like(postId);
|
|
343
377
|
* return { success: true, likeCount: result.likes };
|
|
344
378
|
* },
|
|
@@ -350,5 +384,5 @@ interface WebMCPReturn<TOutputSchema extends JsonSchemaObject | undefined = unde
|
|
|
350
384
|
*/
|
|
351
385
|
declare function useWebMCP<TInputSchema extends ToolInputSchema = InputSchema, TOutputSchema extends JsonSchemaObject | undefined = undefined>(config: WebMCPConfig<TInputSchema, TOutputSchema>, deps?: DependencyList): WebMCPReturn<TOutputSchema>;
|
|
352
386
|
//#endregion
|
|
353
|
-
export { type InferOutput, type InferToolInput, type ToolExecutionState, type WebMCPConfig, type WebMCPReturn, useWebMCP };
|
|
387
|
+
export { type InferOutput, type InferToolInput, type ToolExecuteFunction, type ToolExecutionState, type WebMCPConfig, type WebMCPReturn, useWebMCP };
|
|
354
388
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/useWebMCP.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAmBA;;;;;;;AAK+B,KALnB,cAKmB,CAAA,CAAA,CAAA,GALC,CAKD,SAAA;EAAzB,SAAA,WAAA,EAAA;IACA,SAAA,KAAA,CAAA,EAAA,KAAA,MAAA;EAAM,CAAA;AAaZ,CAAA,GAlBI,KAkBQ,SAAW;EACC,SAAA,KAAA,EAAA,KAAA,EAAA;CAEpB,GApBE,CAoBF,GAnBE,MAmBF,CAAA,MAAA,EAAA,OAAA,CAAA,GAlBA,CAkBA,SAlBU,WAkBV,GAjBE,wBAiBF,CAjB2B,CAiB3B,CAAA,GAhBE,MAgBF,CAAA,MAAA,EAAA,OAAA,CAAA;;;;;;AASJ;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/useWebMCP.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAmBA;;;;;;;AAK+B,KALnB,cAKmB,CAAA,CAAA,CAAA,GALC,CAKD,SAAA;EAAzB,SAAA,WAAA,EAAA;IACA,SAAA,KAAA,CAAA,EAAA,KAAA,MAAA;EAAM,CAAA;AAaZ,CAAA,GAlBI,KAkBQ,SAAW;EACC,SAAA,KAAA,EAAA,KAAA,EAAA;CAEpB,GApBE,CAoBF,GAnBE,MAmBF,CAAA,MAAA,EAAA,OAAA,CAAA,GAlBA,CAkBA,SAlBU,WAkBV,GAjBE,wBAiBF,CAjB2B,CAiB3B,CAAA,GAhBE,MAgBF,CAAA,MAAA,EAAA,OAAA,CAAA;;;;;;AASJ;AAoCA;;;;;AAIS,KApDG,WAoDH,CAAA,sBAnDe,gBAmDf,GAAA,SAAA,GAAA,SAAA,EAAA,YAAA,OAAA,CAAA,GAjDL,aAiDK,SAjDiB,gBAiDjB,GAjDoC,eAiDpC,CAjDoD,aAiDpD,CAAA,GAjDqE,SAiDrE;;;;;;;AAC6D;AAuD/C,UAhGN,kBAgGM,CAAA,UAAA,OAAA,CAAA,CAAA;EAAkB;;;;EAkEzB,WAAA,EAAA,OAAA;EAYsB;;;;EAkBlB,UAAA,EArLN,OAqLM,GAAA,IAAA;EAAK;AAAA;;;EAKD,KAAA,EApLf,KAoLe,GAAA,IAAA;EAMW;;;;EAIe,cAAA,EAAA,MAAA;;;;;;;;;AA2BlD;;;AAEwB,KAxMZ,mBAwMY,CAAA,qBAvMD,eAuMC,GAvMiB,WAuMjB,EAAA,sBAtMA,gBAsMA,GAAA,SAAA,GAAA,SAAA,CAAA,GAAA,CAAA,KAAA,EApMf,cAoMe,CApMA,YAoMA,CAAA,EAAA,GAnMnB,OAmMmB,CAnMX,WAmMW,CAnMC,aAmMD,CAAA,CAAA,GAnMmB,WAmMnB,CAnM+B,aAmM/B,CAAA;;;;;;;;AAWxB;;;;;;;;;;;;ACvIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UDjBU,sCACa,kBAAkB,mCACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAmCR;;;;;;;;;;;;;;;;;;;;;;;iBAwBC;;;;;gBAMD;;;;;;;;;;;0BAYU,YAAY;;;;;;;;uBASf,YAAY;;;;;;;;oBASf;;KAGf,gDACkB,kBAAkB,mCACjB;;;;WAMT,oBAAoB,cAAc;;;;YAIjC,oBAAoB,cAAc;;;;;WAMnC,oBAAoB,cAAc;;;;YAIjC,oBAAoB,cAAc;;;;;;;;;;;;;;;;KAiBtC,kCACW,kBAAkB,mCACjB,4CACpB,iBAAiB,cAAc,iBACjC,2BAA2B,cAAc;;;;;;;;UAS1B,mCAAmC;;;;;SAK3C,mBAAmB,YAAY;;;;;;;;;+BAUT,QAAQ,YAAY;;;;;;;;;;AArSnD;;;;;;;;;;;AAmBA;;;;;;;;AAYA;AAoCA;;;;;;;;;;;;AAKsE;;;;;;;;;;;;;AAuJ7C;;;;;;;;;;;;;;;;;AA0CzB;;;;;;;;;;;AAaA;;;;;;;;;;;;ACvIA;;;;;;;;;;;;;;;;;;iBAAgB,+BACO,kBAAkB,mCACjB,kDAEd,aAAa,cAAc,uBAC5B,iBACN,aAAa"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use client";import{useCallback as e,useEffect as t,useLayoutEffect as n,useRef as r,useState as i}from"react";function a(e){return typeof e==`string`?e:JSON.stringify(e,null,2)}const o=new Map;function s(e){if(!e||typeof e!=`object`||Array.isArray(e))return null;try{let t=JSON.parse(JSON.stringify(e));return!t||typeof t!=`object`||Array.isArray(t)?null:t}catch{return null}}const c=typeof window<`u`?n:t;function l(){let e=globalThis.process?.env?.NODE_ENV;return e===void 0?!1:e!==`production`}function u(n,u){let{name:d,description:f,inputSchema:p,outputSchema:m,annotations:h,
|
|
1
|
+
"use client";import{useCallback as e,useEffect as t,useLayoutEffect as n,useRef as r,useState as i}from"react";function a(e){return typeof e==`string`?e:JSON.stringify(e,null,2)}const o=new Map;function s(e){if(!e||typeof e!=`object`||Array.isArray(e))return null;try{let t=JSON.parse(JSON.stringify(e));return!t||typeof t!=`object`||Array.isArray(t)?null:t}catch{return null}}const c=typeof window<`u`?n:t;function l(){let e=globalThis.process?.env?.NODE_ENV;return e===void 0?!1:e!==`production`}function u(n,u){let{name:d,description:f,inputSchema:p,outputSchema:m,annotations:h,execute:g,handler:_,formatOutput:v=a,onSuccess:y,onError:b}=n,x=g??_;if(!x)throw TypeError(`[useWebMCP] Tool "${d}" must provide an implementation via config.execute or config.handler`);let[S,C]=i({isExecuting:!1,lastResult:null,error:null,executionCount:0}),w=r(x),T=r(y),E=r(b),D=r(v),O=r(!0),k=r(new Set),A=r({inputSchema:p,outputSchema:m,annotations:h,description:f,deps:u});c(()=>{w.current=x,T.current=y,E.current=b,D.current=v},[x,y,b,v]),t(()=>(O.current=!0,()=>{O.current=!1}),[]),t(()=>{if(!l()){A.current={inputSchema:p,outputSchema:m,annotations:h,description:f,deps:u};return}let e=(e,t)=>{k.current.has(e)||(console.warn(`[useWebMCP] ${t}`),k.current.add(e))},t=A.current;p&&t.inputSchema&&t.inputSchema!==p&&e(`inputSchema`,`Tool "${d}" inputSchema reference changed; memoize or define it outside the component to avoid re-registration.`),m&&t.outputSchema&&t.outputSchema!==m&&e(`outputSchema`,`Tool "${d}" outputSchema reference changed; memoize or define it outside the component to avoid re-registration.`),h&&t.annotations&&t.annotations!==h&&e(`annotations`,`Tool "${d}" annotations reference changed; memoize or define it outside the component to avoid re-registration.`),f!==t.description&&e(`description`,`Tool "${d}" description changed; this re-registers the tool. Memoize the description if it does not need to update.`),u?.some(e=>typeof e==`object`&&!!e||typeof e==`function`)&&e(`deps`,`Tool "${d}" deps contains non-primitive values; prefer primitives or memoize objects/functions to reduce re-registration.`),A.current={inputSchema:p,outputSchema:m,annotations:h,description:f,deps:u}},[h,u,f,p,d,m]);let j=e(async e=>{C(e=>({...e,isExecuting:!0,error:null}));try{let t=await w.current(e);return O.current&&C(e=>({isExecuting:!1,lastResult:t,error:null,executionCount:e.executionCount+1})),T.current&&T.current(t,e),t}catch(t){let n=t instanceof Error?t:Error(String(t));throw O.current&&C(e=>({...e,isExecuting:!1,error:n})),E.current&&E.current(n,e),n}},[]),M=r(j);t(()=>{M.current=j},[j]);let N=e(e=>M.current(e),[]),P=e(()=>{C({isExecuting:!1,lastResult:null,error:null,executionCount:0})},[]);return t(()=>{if(typeof window>`u`||!window.navigator?.modelContext){console.warn(`[useWebMCP] window.navigator.modelContext is not available. Tool "${d}" will not be registered.`);return}let e=async e=>{try{let t=await M.current(e),n={content:[{type:`text`,text:D.current(t)}]};if(m){let e=s(t);if(!e)throw Error(`Tool "${d}" outputSchema requires the tool implementation to return a JSON object result`);n.structuredContent=e}return n}catch(e){return{content:[{type:`text`,text:`Error: ${e instanceof Error?e.message:String(e)}`}],isError:!0}}},t=Symbol(d),n=window.navigator.modelContext;return n.registerTool({name:d,description:f,...p&&{inputSchema:p},...m&&{outputSchema:m},...h&&{annotations:h},execute:e}),o.set(d,t),()=>{if(o.get(d)===t){o.delete(d);try{n.unregisterTool(d)}catch(e){l()&&console.warn(`[useWebMCP] Failed to unregister tool "${d}" during cleanup:`,e)}}}},[d,f,p,m,h,...u??[]]),{state:S,execute:N,reset:P}}export{u as useWebMCP};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["response: CallToolResult"],"sources":["../src/useWebMCP.ts"],"sourcesContent":["import type { ToolInputSchema } from '@mcp-b/webmcp-polyfill';\nimport type {\n CallToolResult,\n InputSchema,\n JsonSchemaObject,\n ToolDescriptor,\n} from '@mcp-b/webmcp-types';\nimport type { DependencyList } from 'react';\nimport { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport type {\n InferOutput,\n InferToolInput,\n ToolExecutionState,\n WebMCPConfig,\n WebMCPReturn,\n} from './types.js';\n\n/**\n * Default output formatter that converts values to formatted JSON strings.\n *\n * String values are returned as-is; all other types are serialized to\n * indented JSON for readability.\n *\n * @internal\n */\nfunction defaultFormatOutput(output: unknown): string {\n if (typeof output === 'string') {\n return output;\n }\n return JSON.stringify(output, null, 2);\n}\n\nconst TOOL_OWNER_BY_NAME = new Map<string, symbol>();\ntype StructuredContent = Exclude<CallToolResult['structuredContent'], undefined>;\n\nfunction toStructuredContent(value: unknown): StructuredContent | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return null;\n }\n\n try {\n const normalized = JSON.parse(JSON.stringify(value)) as unknown;\n if (!normalized || typeof normalized !== 'object' || Array.isArray(normalized)) {\n return null;\n }\n return normalized as StructuredContent;\n } catch {\n return null;\n }\n}\n\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n\nfunction isDev(): boolean {\n const env = (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env?.NODE_ENV;\n return env !== undefined ? env !== 'production' : false;\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 * - Handles tool execution and lifecycle callbacks\n * - Automatically unregisters on component unmount\n * - Returns `structuredContent` when `outputSchema` is defined\n *\n * ## Output Schema (Recommended)\n *\n * Always define an `outputSchema` for your tools. This provides:\n * - **Type Safety**: Handler return type is inferred from the schema\n * - **MCP structuredContent**: AI models receive structured, typed data\n * - **Better AI Understanding**: Models can reason about your tool's output format\n *\n * ```tsx\n * useWebMCP({\n * name: 'get_user',\n * description: 'Get user by ID',\n * inputSchema: {\n * type: 'object',\n * properties: { userId: { type: 'string' } },\n * required: ['userId'],\n * } as const,\n * outputSchema: {\n * type: 'object',\n * properties: {\n * id: { type: 'string' },\n * name: { type: 'string' },\n * email: { type: 'string' },\n * },\n * } as const,\n * handler: async ({ userId }) => {\n * const user = await fetchUser(userId);\n * return { id: user.id, name: user.name, email: user.email };\n * },\n * });\n * ```\n *\n * ## Re-render Optimization\n *\n * This hook is optimized to minimize unnecessary tool re-registrations:\n *\n * - **Ref-based callbacks**: `handler`, `onSuccess`, `onError`, and `formatOutput`\n * are stored in refs, so changing these functions won't trigger re-registration.\n *\n * **IMPORTANT**: If `inputSchema`, `outputSchema`, or `annotations` are defined inline\n * or change on every render, the tool will re-register unnecessarily. To avoid this,\n * define them outside your component with `as const`:\n *\n * ```tsx\n * // Good: Static schema defined outside component\n * const OUTPUT_SCHEMA = {\n * type: 'object',\n * properties: { count: { type: 'number' } },\n * } as const;\n *\n * // Bad: Inline schema (creates new object every render)\n * useWebMCP({\n * outputSchema: { type: 'object', properties: { count: { type: 'number' } } } as const,\n * });\n * ```\n *\n * @template TInputSchema - JSON Schema defining input parameter types (use `as const` for inference)\n * @template TOutputSchema - JSON Schema object defining output structure (enables structuredContent)\n *\n * @param config - Configuration object for the tool\n * @param deps - Optional dependency array that triggers tool re-registration when values change.\n *\n * @returns Object containing execution state and control methods\n *\n * @public\n *\n * @example\n * Basic tool with outputSchema (recommended):\n * ```tsx\n * function PostActions() {\n * const likeTool = useWebMCP({\n * name: 'posts_like',\n * description: 'Like a post by ID',\n * inputSchema: {\n * type: 'object',\n * properties: { postId: { type: 'string', description: 'The post ID' } },\n * required: ['postId'],\n * } as const,\n * outputSchema: {\n * type: 'object',\n * properties: {\n * success: { type: 'boolean' },\n * likeCount: { type: 'number' },\n * },\n * } as const,\n * handler: async ({ postId }) => {\n * const result = await api.posts.like(postId);\n * return { success: true, likeCount: result.likes };\n * },\n * });\n *\n * return <div>Likes: {likeTool.state.lastResult?.likeCount ?? 0}</div>;\n * }\n * ```\n */\nexport function useWebMCP<\n TInputSchema extends ToolInputSchema = InputSchema,\n TOutputSchema extends JsonSchemaObject | undefined = undefined,\n>(\n config: WebMCPConfig<TInputSchema, TOutputSchema>,\n deps?: DependencyList\n): WebMCPReturn<TOutputSchema> {\n type TOutput = InferOutput<TOutputSchema>;\n type TInput = InferToolInput<TInputSchema>;\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 const isMountedRef = useRef(true);\n const warnedRef = useRef(new Set<string>());\n const prevConfigRef = useRef({\n inputSchema,\n outputSchema,\n annotations,\n description,\n deps,\n });\n // Update refs when callbacks change (doesn't trigger re-registration)\n useIsomorphicLayoutEffect(() => {\n handlerRef.current = handler;\n onSuccessRef.current = onSuccess;\n onErrorRef.current = onError;\n formatOutputRef.current = formatOutput;\n }, [handler, onSuccess, onError, formatOutput]);\n\n // Cleanup: mark component as unmounted\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n if (!isDev()) {\n prevConfigRef.current = { inputSchema, outputSchema, annotations, description, deps };\n return;\n }\n\n const warnOnce = (key: string, message: string) => {\n if (warnedRef.current.has(key)) {\n return;\n }\n console.warn(`[useWebMCP] ${message}`);\n warnedRef.current.add(key);\n };\n\n const prev = prevConfigRef.current;\n\n if (inputSchema && prev.inputSchema && prev.inputSchema !== inputSchema) {\n warnOnce(\n 'inputSchema',\n `Tool \"${name}\" inputSchema reference changed; memoize or define it outside the component to avoid re-registration.`\n );\n }\n\n if (outputSchema && prev.outputSchema && prev.outputSchema !== outputSchema) {\n warnOnce(\n 'outputSchema',\n `Tool \"${name}\" outputSchema reference changed; memoize or define it outside the component to avoid re-registration.`\n );\n }\n\n if (annotations && prev.annotations && prev.annotations !== annotations) {\n warnOnce(\n 'annotations',\n `Tool \"${name}\" annotations reference changed; memoize or define it outside the component to avoid re-registration.`\n );\n }\n\n if (description !== prev.description) {\n warnOnce(\n 'description',\n `Tool \"${name}\" description changed; this re-registers the tool. Memoize the description if it does not need to update.`\n );\n }\n\n if (\n deps?.some(\n (value) => (typeof value === 'object' && value !== null) || typeof value === 'function'\n )\n ) {\n warnOnce(\n 'deps',\n `Tool \"${name}\" deps contains non-primitive values; prefer primitives or memoize objects/functions to reduce re-registration.`\n );\n }\n\n prevConfigRef.current = { inputSchema, outputSchema, annotations, description, deps };\n }, [annotations, deps, description, inputSchema, name, outputSchema]);\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(async (input: unknown): Promise<TOutput> => {\n setState((prev) => ({\n ...prev,\n isExecuting: true,\n error: null,\n }));\n\n try {\n const result = await handlerRef.current(input as TInput);\n\n // Only update state if component is still mounted\n if (isMountedRef.current) {\n setState((prev) => ({\n isExecuting: false,\n lastResult: result,\n error: null,\n executionCount: prev.executionCount + 1,\n }));\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 // Only update state if component is still mounted\n if (isMountedRef.current) {\n setState((prev) => ({\n ...prev,\n isExecuting: false,\n error: err,\n }));\n }\n\n if (onErrorRef.current) {\n onErrorRef.current(err, input);\n }\n\n throw err;\n }\n }, []);\n const executeRef = useRef(execute);\n\n useEffect(() => {\n executeRef.current = execute;\n }, [execute]);\n\n const stableExecute = useCallback(\n (input: unknown): Promise<TOutput> => executeRef.current(input),\n []\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 /**\n * Handles MCP tool execution by running the handler and formatting the response.\n *\n * @param input - The input parameters from the MCP client\n * @returns CallToolResult with text content and optional structuredContent\n */\n const mcpHandler = async (input: unknown): Promise<CallToolResult> => {\n try {\n const result = await executeRef.current(input);\n const formattedOutput = formatOutputRef.current(result);\n\n const response: CallToolResult = {\n content: [\n {\n type: 'text',\n text: formattedOutput,\n },\n ],\n };\n\n if (outputSchema) {\n const structuredContent = toStructuredContent(result);\n if (!structuredContent) {\n throw new Error(\n `Tool \"${name}\" outputSchema requires the handler to return a JSON object result`\n );\n }\n response.structuredContent = structuredContent;\n }\n\n return response;\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 ownerToken = Symbol(name);\n const modelContext = window.navigator.modelContext;\n\n (modelContext.registerTool as (tool: ToolDescriptor) => void)({\n name,\n description,\n ...(inputSchema && { inputSchema: inputSchema as InputSchema }),\n ...(outputSchema && { outputSchema: outputSchema as InputSchema }),\n ...(annotations && { annotations }),\n execute: mcpHandler,\n });\n TOOL_OWNER_BY_NAME.set(name, ownerToken);\n\n return () => {\n const currentOwner = TOOL_OWNER_BY_NAME.get(name);\n if (currentOwner !== ownerToken) {\n return;\n }\n\n TOOL_OWNER_BY_NAME.delete(name);\n try {\n modelContext.unregisterTool(name);\n } catch (error) {\n if (isDev()) {\n console.warn(`[useWebMCP] Failed to unregister tool \"${name}\" during cleanup:`, error);\n }\n }\n };\n // Spread operator in dependencies: Allows users to provide additional dependencies\n // via the `deps` parameter. While unconventional, this pattern is intentional to support\n // dynamic dependency injection. The spread is safe because deps is validated and warned\n // about non-primitive values earlier in this hook.\n }, [name, description, inputSchema, outputSchema, annotations, ...(deps ?? [])]);\n\n return {\n state,\n execute: stableExecute,\n reset,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["response: CallToolResult"],"sources":["../src/useWebMCP.ts"],"sourcesContent":["import type { ToolInputSchema } from '@mcp-b/webmcp-polyfill';\nimport type {\n CallToolResult,\n InputSchema,\n JsonSchemaObject,\n ToolDescriptor,\n} from '@mcp-b/webmcp-types';\nimport type { DependencyList } from 'react';\nimport { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport type {\n InferOutput,\n InferToolInput,\n ToolExecutionState,\n WebMCPConfig,\n WebMCPReturn,\n} from './types.js';\n\n/**\n * Default output formatter that converts values to formatted JSON strings.\n *\n * String values are returned as-is; all other types are serialized to\n * indented JSON for readability.\n *\n * @internal\n */\nfunction defaultFormatOutput(output: unknown): string {\n if (typeof output === 'string') {\n return output;\n }\n return JSON.stringify(output, null, 2);\n}\n\nconst TOOL_OWNER_BY_NAME = new Map<string, symbol>();\ntype StructuredContent = Exclude<CallToolResult['structuredContent'], undefined>;\n\nfunction toStructuredContent(value: unknown): StructuredContent | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return null;\n }\n\n try {\n const normalized = JSON.parse(JSON.stringify(value)) as unknown;\n if (!normalized || typeof normalized !== 'object' || Array.isArray(normalized)) {\n return null;\n }\n return normalized as StructuredContent;\n } catch {\n return null;\n }\n}\n\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n\nfunction isDev(): boolean {\n const env = (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env?.NODE_ENV;\n return env !== undefined ? env !== 'production' : false;\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 * - Handles tool execution and lifecycle callbacks\n * - Automatically unregisters on component unmount\n * - Returns `structuredContent` when `outputSchema` is defined\n *\n * ## Output Schema (Recommended)\n *\n * Always define an `outputSchema` for your tools. This provides:\n * - **Type Safety**: Handler return type is inferred from the schema\n * - **MCP structuredContent**: AI models receive structured, typed data\n * - **Better AI Understanding**: Models can reason about your tool's output format\n *\n * ```tsx\n * useWebMCP({\n * name: 'get_user',\n * description: 'Get user by ID',\n * inputSchema: {\n * type: 'object',\n * properties: { userId: { type: 'string' } },\n * required: ['userId'],\n * } as const,\n * outputSchema: {\n * type: 'object',\n * properties: {\n * id: { type: 'string' },\n * name: { type: 'string' },\n * email: { type: 'string' },\n * },\n * } as const,\n * execute: async ({ userId }) => {\n * const user = await fetchUser(userId);\n * return { id: user.id, name: user.name, email: user.email };\n * },\n * });\n * ```\n *\n * ## Re-render Optimization\n *\n * This hook is optimized to minimize unnecessary tool re-registrations:\n *\n * - **Ref-based callbacks**: `execute`/`handler`, `onSuccess`, `onError`, and `formatOutput`\n * are stored in refs, so changing these functions won't trigger re-registration.\n *\n * **IMPORTANT**: If `inputSchema`, `outputSchema`, or `annotations` are defined inline\n * or change on every render, the tool will re-register unnecessarily. To avoid this,\n * define them outside your component with `as const`:\n *\n * ```tsx\n * // Good: Static schema defined outside component\n * const OUTPUT_SCHEMA = {\n * type: 'object',\n * properties: { count: { type: 'number' } },\n * } as const;\n *\n * // Bad: Inline schema (creates new object every render)\n * useWebMCP({\n * outputSchema: { type: 'object', properties: { count: { type: 'number' } } } as const,\n * });\n * ```\n *\n * @template TInputSchema - JSON Schema defining input parameter types (use `as const` for inference)\n * @template TOutputSchema - JSON Schema object defining output structure (enables structuredContent)\n *\n * @param config - Configuration object for the tool\n * @param deps - Optional dependency array that triggers tool re-registration when values change.\n *\n * @returns Object containing execution state and control methods\n *\n * @public\n *\n * @example\n * Basic tool with outputSchema (recommended):\n * ```tsx\n * function PostActions() {\n * const likeTool = useWebMCP({\n * name: 'posts_like',\n * description: 'Like a post by ID',\n * inputSchema: {\n * type: 'object',\n * properties: { postId: { type: 'string', description: 'The post ID' } },\n * required: ['postId'],\n * } as const,\n * outputSchema: {\n * type: 'object',\n * properties: {\n * success: { type: 'boolean' },\n * likeCount: { type: 'number' },\n * },\n * } as const,\n * execute: async ({ postId }) => {\n * const result = await api.posts.like(postId);\n * return { success: true, likeCount: result.likes };\n * },\n * });\n *\n * return <div>Likes: {likeTool.state.lastResult?.likeCount ?? 0}</div>;\n * }\n * ```\n */\nexport function useWebMCP<\n TInputSchema extends ToolInputSchema = InputSchema,\n TOutputSchema extends JsonSchemaObject | undefined = undefined,\n>(\n config: WebMCPConfig<TInputSchema, TOutputSchema>,\n deps?: DependencyList\n): WebMCPReturn<TOutputSchema> {\n type TOutput = InferOutput<TOutputSchema>;\n type TInput = InferToolInput<TInputSchema>;\n const {\n name,\n description,\n inputSchema,\n outputSchema,\n annotations,\n execute: configExecute,\n handler: legacyHandler,\n formatOutput = defaultFormatOutput,\n onSuccess,\n onError,\n } = config;\n const toolExecute = configExecute ?? legacyHandler;\n\n if (!toolExecute) {\n throw new TypeError(\n `[useWebMCP] Tool \"${name}\" must provide an implementation via config.execute or config.handler`\n );\n }\n\n const [state, setState] = useState<ToolExecutionState<TOutput>>({\n isExecuting: false,\n lastResult: null,\n error: null,\n executionCount: 0,\n });\n\n const toolExecuteRef = useRef(toolExecute);\n const onSuccessRef = useRef(onSuccess);\n const onErrorRef = useRef(onError);\n const formatOutputRef = useRef(formatOutput);\n const isMountedRef = useRef(true);\n const warnedRef = useRef(new Set<string>());\n const prevConfigRef = useRef({\n inputSchema,\n outputSchema,\n annotations,\n description,\n deps,\n });\n // Update refs when callbacks change (doesn't trigger re-registration)\n useIsomorphicLayoutEffect(() => {\n toolExecuteRef.current = toolExecute;\n onSuccessRef.current = onSuccess;\n onErrorRef.current = onError;\n formatOutputRef.current = formatOutput;\n }, [toolExecute, onSuccess, onError, formatOutput]);\n\n // Cleanup: mark component as unmounted\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n if (!isDev()) {\n prevConfigRef.current = { inputSchema, outputSchema, annotations, description, deps };\n return;\n }\n\n const warnOnce = (key: string, message: string) => {\n if (warnedRef.current.has(key)) {\n return;\n }\n console.warn(`[useWebMCP] ${message}`);\n warnedRef.current.add(key);\n };\n\n const prev = prevConfigRef.current;\n\n if (inputSchema && prev.inputSchema && prev.inputSchema !== inputSchema) {\n warnOnce(\n 'inputSchema',\n `Tool \"${name}\" inputSchema reference changed; memoize or define it outside the component to avoid re-registration.`\n );\n }\n\n if (outputSchema && prev.outputSchema && prev.outputSchema !== outputSchema) {\n warnOnce(\n 'outputSchema',\n `Tool \"${name}\" outputSchema reference changed; memoize or define it outside the component to avoid re-registration.`\n );\n }\n\n if (annotations && prev.annotations && prev.annotations !== annotations) {\n warnOnce(\n 'annotations',\n `Tool \"${name}\" annotations reference changed; memoize or define it outside the component to avoid re-registration.`\n );\n }\n\n if (description !== prev.description) {\n warnOnce(\n 'description',\n `Tool \"${name}\" description changed; this re-registers the tool. Memoize the description if it does not need to update.`\n );\n }\n\n if (\n deps?.some(\n (value) => (typeof value === 'object' && value !== null) || typeof value === 'function'\n )\n ) {\n warnOnce(\n 'deps',\n `Tool \"${name}\" deps contains non-primitive values; prefer primitives or memoize objects/functions to reduce re-registration.`\n );\n }\n\n prevConfigRef.current = { inputSchema, outputSchema, annotations, description, deps };\n }, [annotations, deps, description, inputSchema, name, outputSchema]);\n\n /**\n * Executes the configured tool implementation with input validation and state management.\n *\n * @param input - The input parameters to validate and pass to the tool implementation\n * @returns Promise resolving to the tool output\n * @throws Error if validation fails or the tool implementation throws\n */\n const execute = useCallback(async (input: unknown): Promise<TOutput> => {\n setState((prev) => ({\n ...prev,\n isExecuting: true,\n error: null,\n }));\n\n try {\n const result = await toolExecuteRef.current(input as TInput);\n\n // Only update state if component is still mounted\n if (isMountedRef.current) {\n setState((prev) => ({\n isExecuting: false,\n lastResult: result,\n error: null,\n executionCount: prev.executionCount + 1,\n }));\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 // Only update state if component is still mounted\n if (isMountedRef.current) {\n setState((prev) => ({\n ...prev,\n isExecuting: false,\n error: err,\n }));\n }\n\n if (onErrorRef.current) {\n onErrorRef.current(err, input);\n }\n\n throw err;\n }\n }, []);\n const executeRef = useRef(execute);\n\n useEffect(() => {\n executeRef.current = execute;\n }, [execute]);\n\n const stableExecute = useCallback(\n (input: unknown): Promise<TOutput> => executeRef.current(input),\n []\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 /**\n * Handles MCP tool execution by running the tool implementation and formatting the response.\n *\n * @param input - The input parameters from the MCP client\n * @returns CallToolResult with text content and optional structuredContent\n */\n const mcpHandler = async (input: unknown): Promise<CallToolResult> => {\n try {\n const result = await executeRef.current(input);\n const formattedOutput = formatOutputRef.current(result);\n\n const response: CallToolResult = {\n content: [\n {\n type: 'text',\n text: formattedOutput,\n },\n ],\n };\n\n if (outputSchema) {\n const structuredContent = toStructuredContent(result);\n if (!structuredContent) {\n throw new Error(\n `Tool \"${name}\" outputSchema requires the tool implementation to return a JSON object result`\n );\n }\n response.structuredContent = structuredContent;\n }\n\n return response;\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 ownerToken = Symbol(name);\n const modelContext = window.navigator.modelContext;\n\n (modelContext.registerTool as (tool: ToolDescriptor) => void)({\n name,\n description,\n ...(inputSchema && { inputSchema: inputSchema as InputSchema }),\n ...(outputSchema && { outputSchema: outputSchema as InputSchema }),\n ...(annotations && { annotations }),\n execute: mcpHandler,\n });\n TOOL_OWNER_BY_NAME.set(name, ownerToken);\n\n return () => {\n const currentOwner = TOOL_OWNER_BY_NAME.get(name);\n if (currentOwner !== ownerToken) {\n return;\n }\n\n TOOL_OWNER_BY_NAME.delete(name);\n try {\n modelContext.unregisterTool(name);\n } catch (error) {\n if (isDev()) {\n console.warn(`[useWebMCP] Failed to unregister tool \"${name}\" during cleanup:`, error);\n }\n }\n };\n // Spread operator in dependencies: Allows users to provide additional dependencies\n // via the `deps` parameter. While unconventional, this pattern is intentional to support\n // dynamic dependency injection. The spread is safe because deps is validated and warned\n // about non-primitive values earlier in this hook.\n }, [name, description, inputSchema, outputSchema, annotations, ...(deps ?? [])]);\n\n return {\n state,\n execute: stableExecute,\n reset,\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usewebmcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Standalone React hooks for strict core WebMCP tool registration with navigator.modelContext",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"dist"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@mcp-b/webmcp-
|
|
41
|
-
"@mcp-b/webmcp-
|
|
40
|
+
"@mcp-b/webmcp-types": "0.2.1",
|
|
41
|
+
"@mcp-b/webmcp-polyfill": "0.2.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/node": "22.17.2",
|