@mcp-web/react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +180 -0
  3. package/dist/index.d.ts +6 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +5 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/mcp-web-context.d.ts +16 -0
  8. package/dist/mcp-web-context.d.ts.map +1 -0
  9. package/dist/mcp-web-context.js +7 -0
  10. package/dist/mcp-web-context.js.map +1 -0
  11. package/dist/mcp-web-provider.d.ts +62 -0
  12. package/dist/mcp-web-provider.d.ts.map +1 -0
  13. package/dist/mcp-web-provider.js +54 -0
  14. package/dist/mcp-web-provider.js.map +1 -0
  15. package/dist/use-apps.d.ts +63 -0
  16. package/dist/use-apps.d.ts.map +1 -0
  17. package/dist/use-apps.js +58 -0
  18. package/dist/use-apps.js.map +1 -0
  19. package/dist/use-connected-mcp-web.d.ts +14 -0
  20. package/dist/use-connected-mcp-web.d.ts.map +1 -0
  21. package/dist/use-connected-mcp-web.js +44 -0
  22. package/dist/use-connected-mcp-web.js.map +1 -0
  23. package/dist/use-mcp-apps.d.ts +63 -0
  24. package/dist/use-mcp-apps.d.ts.map +1 -0
  25. package/dist/use-mcp-apps.js +58 -0
  26. package/dist/use-mcp-apps.js.map +1 -0
  27. package/dist/use-mcp-tools.d.ts +99 -0
  28. package/dist/use-mcp-tools.d.ts.map +1 -0
  29. package/dist/use-mcp-tools.js +90 -0
  30. package/dist/use-mcp-tools.js.map +1 -0
  31. package/dist/use-mcp-web.d.ts +18 -0
  32. package/dist/use-mcp-web.d.ts.map +1 -0
  33. package/dist/use-mcp-web.js +25 -0
  34. package/dist/use-mcp-web.js.map +1 -0
  35. package/dist/use-tools.d.ts +78 -0
  36. package/dist/use-tools.d.ts.map +1 -0
  37. package/dist/use-tools.js +64 -0
  38. package/dist/use-tools.js.map +1 -0
  39. package/package.json +31 -0
  40. package/src/index.ts +5 -0
  41. package/src/mcp-web-context.ts +18 -0
  42. package/src/mcp-web-provider.ts +87 -0
  43. package/src/use-connected-mcp-web.ts +49 -0
  44. package/src/use-mcp-apps.ts +141 -0
  45. package/src/use-mcp-tools.ts +221 -0
  46. package/src/use-mcp-web.ts +25 -0
  47. package/tsconfig.json +10 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-tools.js","sourceRoot":"","sources":["../src/use-tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAyFlD,MAAM,UAAU,QAAQ,CACtB,QAAwD,EACxD,GAAG,IAA+C;IAElD,sDAAsD;IACtD,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAmB,EAAE,CAC3D,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,SAAS,IAAI,KAAK;QAClB,eAAe,IAAI,KAAK;QACxB,OAAQ,KAAgB,CAAC,OAAO,KAAK,UAAU,CAAC;IAElD,IAAI,UAA8B,CAAC;IACnC,IAAI,eAAmC,CAAC;IAExC,IAAI,gBAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/B,UAAU,GAAG,QAAQ,CAAC;QACtB,eAAe,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAChC,CAAC;SAAM,CAAC;QACN,UAAU,GAAG,SAAS,CAAC;QACvB,eAAe,GAAG,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/C,CAAC;IAED,0CAA0C;IAC1C,MAAM,OAAO,GAAG,UAAU,CAAC,aAAa,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,UAAU,IAAI,OAAO,EAAE,MAAM,CAAC;IAE7C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,uFAAuF,CACxF,CAAC;IACJ,CAAC;IAED,6CAA6C;IAC7C,MAAM,QAAQ,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;IACzC,QAAQ,CAAC,OAAO,GAAG,eAAe,CAAC;IAEnC,oCAAoC;IACpC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACjC,SAAS,CAAC,OAAO,GAAG,MAAM,CAAC;IAE3B,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,UAAU,GAAmB,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC;QAE9B,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;YACpC,IAAI,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;gBACxB,uBAAuB;gBACvB,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBAClB,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;YAC9D,CAAC;iBAAM,IAAI,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;gBACrC,6BAA6B;gBAC7B,MAAM,CAAC,EAAE,AAAD,EAAG,OAAO,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;gBAC9C,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC3B,CAAC;iBAAM,CAAC;gBACN,8BAA8B;gBAC9B,kHAAkH;gBAClH,GAAG,CAAC,OAAO,CAAC,IAAW,CAAC,CAAC;gBACzB,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;QAED,OAAO,GAAG,EAAE;YACV,KAAK,MAAM,OAAO,IAAI,UAAU,EAAE,CAAC;gBACjC,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IACP,4EAA4E;IAC5E,wEAAwE;IACxE,mCAAmC;AACrC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@mcp-web/react",
3
+ "version": "0.1.0",
4
+ "description": "MCP Web integration for React state management",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "dependencies": {
15
+ "zod": "~4.1.12",
16
+ "@mcp-web/decompose-zod-schema": "0.1.0",
17
+ "@mcp-web/types": "0.1.0",
18
+ "@mcp-web/core": "0.1.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/react": "^18.3.0",
22
+ "typescript": "~5.9.3"
23
+ },
24
+ "peerDependencies": {
25
+ "react": ">=17.0.0"
26
+ },
27
+ "scripts": {
28
+ "build": "tsc",
29
+ "clean": "rm -rf dist"
30
+ }
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export type { MCPWebContextValue } from './mcp-web-context';
2
+ export { MCPWebProvider, type MCPWebProviderProps, type MCPWebProviderPropsWithConfig, type MCPWebProviderPropsWithInstance } from './mcp-web-provider';
3
+ export { useMCPWeb } from './use-mcp-web';
4
+ export { useMCPTools, type RegisterableTool, type UseMCPToolsOptions } from './use-mcp-tools';
5
+ export { useMCPApps, type RegisterableApp } from './use-mcp-apps';
@@ -0,0 +1,18 @@
1
+ import type { MCPWeb } from "@mcp-web/core";
2
+ import { createContext } from "react";
3
+
4
+ /**
5
+ * Context value provided by MCPWebProvider.
6
+ */
7
+ export interface MCPWebContextValue {
8
+ /** The MCPWeb instance for registering tools and making queries. */
9
+ mcpWeb: MCPWeb;
10
+ /** Whether the MCPWeb instance is connected to the bridge server. */
11
+ isConnected: boolean;
12
+ }
13
+
14
+ /**
15
+ * React context for sharing MCPWeb instance across component tree.
16
+ * @internal Use MCPWebProvider and useMCPWeb instead of accessing directly.
17
+ */
18
+ export const MCPWebContext = createContext<MCPWebContextValue | null>(null);
@@ -0,0 +1,87 @@
1
+ import { MCPWeb } from "@mcp-web/core";
2
+ import type { MCPWebConfig } from "@mcp-web/types";
3
+ import React, { type ReactNode, useMemo } from "react";
4
+ import { MCPWebContext } from "./mcp-web-context";
5
+ import { useConnectedMCPWeb } from "./use-connected-mcp-web";
6
+
7
+ /**
8
+ * Props for MCPWebProvider when creating a new MCPWeb instance from config.
9
+ */
10
+ export interface MCPWebProviderPropsWithConfig {
11
+ /** Child components that can access MCPWeb via useMCPWeb hook. */
12
+ children: ReactNode;
13
+ /** Configuration for creating a new MCPWeb instance. */
14
+ config: MCPWebConfig;
15
+ }
16
+
17
+ /**
18
+ * Props for MCPWebProvider when using an existing MCPWeb instance.
19
+ */
20
+ export interface MCPWebProviderPropsWithInstance {
21
+ /** Child components that can access MCPWeb via useMCPWeb hook. */
22
+ children: ReactNode;
23
+ /** Existing MCPWeb instance to provide to children. */
24
+ mcpWeb: MCPWeb;
25
+ }
26
+
27
+ /**
28
+ * Props for MCPWebProvider component.
29
+ * Either provide a `config` to create a new instance, or `mcpWeb` to use an existing one.
30
+ */
31
+ export type MCPWebProviderProps = MCPWebProviderPropsWithConfig | MCPWebProviderPropsWithInstance;
32
+
33
+ /**
34
+ * Provider component that shares an MCPWeb instance across the component tree.
35
+ *
36
+ * Handles MCPWeb instantiation (if config provided) and connection lifecycle
37
+ * automatically. All child components can access the MCPWeb instance via the
38
+ * `useMCPWeb` hook.
39
+ *
40
+ * @example With config (creates new instance)
41
+ * ```tsx
42
+ * function Root() {
43
+ * return (
44
+ * <MCPWebProvider config={{
45
+ * name: 'My App',
46
+ * description: 'My app description',
47
+ * }}>
48
+ * <App />
49
+ * </MCPWebProvider>
50
+ * );
51
+ * }
52
+ * ```
53
+ *
54
+ * @example With existing instance
55
+ * ```tsx
56
+ * const mcpWeb = new MCPWeb(config);
57
+ *
58
+ * function Root() {
59
+ * return (
60
+ * <MCPWebProvider mcpWeb={mcpWeb}>
61
+ * <App />
62
+ * </MCPWebProvider>
63
+ * );
64
+ * }
65
+ * ```
66
+ */
67
+ export function MCPWebProvider({ children, ...props }: MCPWebProviderProps) {
68
+ const mcpWeb = 'mcpWeb' in props ? props.mcpWeb : undefined;
69
+ const config = 'config' in props ? props.config : undefined;
70
+
71
+ const mcpInstance = useMemo(() => {
72
+ if (mcpWeb) {
73
+ return mcpWeb;
74
+ }
75
+ if (config) {
76
+ return new MCPWeb(config);
77
+ }
78
+ throw new Error('MCPWebProvider requires either mcpWeb or config prop');
79
+ }, [mcpWeb, config]);
80
+ const mcpState = useConnectedMCPWeb(mcpInstance);
81
+
82
+ return React.createElement(
83
+ MCPWebContext.Provider,
84
+ { value: mcpState },
85
+ children
86
+ );
87
+ }
@@ -0,0 +1,49 @@
1
+ import type { MCPWeb } from "@mcp-web/core";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import type { MCPWebContextValue } from "./mcp-web-context";
4
+
5
+ /**
6
+ * Internal hook for managing MCPWeb connection lifecycle.
7
+ * Connects on mount and disconnects on unmount.
8
+ * Returns reactive connection state for triggering re-renders.
9
+ *
10
+ * Handles React StrictMode's double-mount behavior by using a ref to track
11
+ * whether we should actually disconnect on cleanup.
12
+ *
13
+ * @internal
14
+ */
15
+ export function useConnectedMCPWeb(mcpInstance: MCPWeb): MCPWebContextValue {
16
+ const [isConnected, setIsConnected] = useState(mcpInstance.connected);
17
+ // Track if the effect has been cleaned up to handle StrictMode double-mount
18
+ const cleanedUpRef = useRef(false);
19
+
20
+ useEffect(() => {
21
+ // Reset the cleanup flag on mount
22
+ cleanedUpRef.current = false;
23
+
24
+ if (!mcpInstance.connected) {
25
+ mcpInstance.connect().then(() => {
26
+ // Only update state if we haven't been cleaned up
27
+ if (!cleanedUpRef.current) {
28
+ setIsConnected(true);
29
+ }
30
+ });
31
+ } else {
32
+ setIsConnected(true);
33
+ }
34
+
35
+ return () => {
36
+ cleanedUpRef.current = true;
37
+ // Use setTimeout to defer disconnect, allowing StrictMode's remount to cancel it
38
+ // If the component remounts (StrictMode), the new effect will run before this timeout
39
+ setTimeout(() => {
40
+ // Only disconnect if we're still in a cleaned-up state (no remount happened)
41
+ if (cleanedUpRef.current) {
42
+ mcpInstance.disconnect();
43
+ }
44
+ }, 0);
45
+ };
46
+ }, [mcpInstance]);
47
+
48
+ return { mcpWeb: mcpInstance, isConnected };
49
+ }
@@ -0,0 +1,141 @@
1
+ import type { MCPWeb } from '@mcp-web/core';
2
+ import type { AppDefinition, CreatedApp } from '@mcp-web/types';
3
+ import { isCreatedApp } from '@mcp-web/types';
4
+ import { useContext, useEffect, useRef } from 'react';
5
+ import { MCPWebContext } from './mcp-web-context';
6
+
7
+ /**
8
+ * An app that can be registered with useApps.
9
+ * Can be a CreatedApp or a raw AppDefinition.
10
+ */
11
+ export type RegisterableApp = CreatedApp | AppDefinition;
12
+
13
+ /**
14
+ * Hook for registering MCP Apps with automatic cleanup on unmount.
15
+ *
16
+ * This is the recommended way to register MCP Apps in React applications.
17
+ * Apps are registered when the component mounts and automatically
18
+ * unregistered when the component unmounts.
19
+ *
20
+ * MCP Apps are visual UI components that AI can render inline in chat
21
+ * interfaces like Claude Desktop.
22
+ *
23
+ * @example Basic usage with createApp
24
+ * ```tsx
25
+ * // apps.ts
26
+ * import { createApp } from '@mcp-web/app';
27
+ *
28
+ * export const statisticsApp = createApp({
29
+ * name: 'show_statistics',
30
+ * description: 'Display statistics visualization',
31
+ * handler: () => ({
32
+ * totalTasks: todos.length,
33
+ * completedTasks: completedTodos.length,
34
+ * }),
35
+ * });
36
+ *
37
+ * // App.tsx
38
+ * import { useMCPApps } from '@mcp-web/react';
39
+ * import { statisticsApp } from './apps';
40
+ *
41
+ * function App() {
42
+ * useMCPApps(statisticsApp);
43
+ * return <div>...</div>;
44
+ * }
45
+ * ```
46
+ *
47
+ * @example Multiple apps
48
+ * ```tsx
49
+ * function App() {
50
+ * useMCPApps(statisticsApp, chartApp, dashboardApp);
51
+ * return <div>...</div>;
52
+ * }
53
+ * ```
54
+ *
55
+ * @example Conditional app registration
56
+ * ```tsx
57
+ * function Analytics() {
58
+ * // This app only exists while Analytics is mounted
59
+ * useMCPApps(analyticsApp);
60
+ * return <div>Analytics enabled</div>;
61
+ * }
62
+ * ```
63
+ *
64
+ * @param apps - Apps to register (variadic or array)
65
+ */
66
+ export function useMCPApps(
67
+ ...apps: (RegisterableApp | RegisterableApp[])[]
68
+ ): void;
69
+
70
+ export function useMCPApps(
71
+ mcpWeb: MCPWeb,
72
+ ...apps: (RegisterableApp | RegisterableApp[])[]
73
+ ): void;
74
+
75
+ export function useMCPApps(
76
+ firstArg: MCPWeb | RegisterableApp | RegisterableApp[],
77
+ ...rest: (RegisterableApp | RegisterableApp[])[]
78
+ ): void {
79
+ // Determine if first arg is MCPWeb instance or an app
80
+ const isMCPWebInstance = (value: unknown): value is MCPWeb =>
81
+ typeof value === 'object' &&
82
+ value !== null &&
83
+ 'addApp' in value &&
84
+ 'removeApp' in value &&
85
+ typeof (value as MCPWeb).addApp === 'function';
86
+
87
+ let mcpWebProp: MCPWeb | undefined;
88
+ let appsToRegister: RegisterableApp[];
89
+
90
+ if (isMCPWebInstance(firstArg)) {
91
+ mcpWebProp = firstArg;
92
+ appsToRegister = rest.flat();
93
+ } else {
94
+ mcpWebProp = undefined;
95
+ appsToRegister = [firstArg, ...rest].flat();
96
+ }
97
+
98
+ // Try to get from context if not provided
99
+ const context = useContext(MCPWebContext);
100
+ const mcpWeb = mcpWebProp ?? context?.mcpWeb;
101
+
102
+ if (!mcpWeb) {
103
+ throw new Error(
104
+ 'useApps requires either mcpWeb as first argument or MCPWebProvider in component tree'
105
+ );
106
+ }
107
+
108
+ // Keep a stable reference to the apps array
109
+ const appsRef = useRef(appsToRegister);
110
+ appsRef.current = appsToRegister;
111
+
112
+ // Keep a stable reference to mcpWeb
113
+ const mcpWebRef = useRef(mcpWeb);
114
+ mcpWebRef.current = mcpWeb;
115
+
116
+ useEffect(() => {
117
+ const cleanupFns: (() => void)[] = [];
118
+ const mcp = mcpWebRef.current;
119
+
120
+ for (const app of appsRef.current) {
121
+ if (isCreatedApp(app)) {
122
+ // Register CreatedApp
123
+ mcp.addApp(app);
124
+ cleanupFns.push(() => mcp.removeApp(app.definition.name));
125
+ } else {
126
+ // Register raw AppDefinition
127
+ mcp.addApp(app);
128
+ cleanupFns.push(() => mcp.removeApp(app.name));
129
+ }
130
+ }
131
+
132
+ return () => {
133
+ for (const cleanup of cleanupFns) {
134
+ cleanup();
135
+ }
136
+ };
137
+ }, []);
138
+ // Note: Empty deps because we use refs - apps are registered once on mount
139
+ // and cleaned up on unmount. If you need to re-register on app change,
140
+ // use a key prop on the component.
141
+ }
@@ -0,0 +1,221 @@
1
+ import type { CreatedStateTools, CreatedTool, MCPWeb, ToolRegistrationError } from '@mcp-web/core';
2
+ import { isCreatedStateTools, isCreatedTool } from '@mcp-web/core';
3
+ import type { ToolDefinition } from '@mcp-web/types';
4
+ import { useContext, useEffect, useRef } from 'react';
5
+ import { MCPWebContext } from './mcp-web-context';
6
+
7
+ /**
8
+ * A tool that can be registered with useMCPTools.
9
+ * Can be a CreatedTool, CreatedStateTools, or a raw ToolDefinition.
10
+ */
11
+ export type RegisterableTool =
12
+ | CreatedTool
13
+ // biome-ignore lint/suspicious/noExplicitAny: Need any to handle variance in generic state types
14
+ | CreatedStateTools<any>
15
+ | ToolDefinition;
16
+
17
+ /**
18
+ * Options for the useMCPTools hook.
19
+ */
20
+ export interface UseMCPToolsOptions {
21
+ /** Called if the bridge rejects any tool registration (e.g., schema conflict with a sibling session). */
22
+ onRegistrationError?: (error: ToolRegistrationError) => void;
23
+ }
24
+
25
+ /**
26
+ * Hook for registering MCP tools with automatic cleanup on unmount.
27
+ *
28
+ * This is the recommended way to register tools in React applications.
29
+ * Tools are registered when the component mounts and automatically
30
+ * unregistered when the component unmounts.
31
+ *
32
+ * @example Basic usage with created tools
33
+ * ```tsx
34
+ * // tools.ts
35
+ * import { createTool, createStateTools } from '@mcp-web/core';
36
+ *
37
+ * export const timeTool = createTool({
38
+ * name: 'get_time',
39
+ * description: 'Get current time',
40
+ * handler: () => new Date().toISOString(),
41
+ * });
42
+ *
43
+ * export const todoTools = createStateTools({
44
+ * name: 'todos',
45
+ * description: 'Todo list',
46
+ * get: () => store.get(todosAtom),
47
+ * set: (value) => store.set(todosAtom, value),
48
+ * schema: TodosSchema,
49
+ * expand: true,
50
+ * });
51
+ *
52
+ * // App.tsx
53
+ * import { useMCPTools } from '@mcp-web/react';
54
+ * import { timeTool, todoTools } from './tools';
55
+ *
56
+ * function App() {
57
+ * useMCPTools(timeTool, todoTools);
58
+ * return <div>...</div>;
59
+ * }
60
+ * ```
61
+ *
62
+ * @example With registration error handling
63
+ * ```tsx
64
+ * function App() {
65
+ * useMCPTools(myTool, {
66
+ * onRegistrationError: (error) => {
67
+ * console.error(`Tool ${error.toolName} rejected: ${error.message}`);
68
+ * },
69
+ * });
70
+ * return <div>...</div>;
71
+ * }
72
+ * ```
73
+ *
74
+ * @example Conditional tool registration
75
+ * ```tsx
76
+ * function AdminPanel() {
77
+ * // These tools only exist while AdminPanel is mounted
78
+ * useMCPTools(adminTools);
79
+ * return <div>Admin controls</div>;
80
+ * }
81
+ *
82
+ * function App() {
83
+ * const [isAdmin, setIsAdmin] = useState(false);
84
+ * return (
85
+ * <div>
86
+ * {isAdmin && <AdminPanel />}
87
+ * </div>
88
+ * );
89
+ * }
90
+ * ```
91
+ *
92
+ * @example With array of tools
93
+ * ```tsx
94
+ * const allTools = [todoTools, projectTools, settingsTools];
95
+ *
96
+ * function App() {
97
+ * useMCPTools(allTools);
98
+ * // or: useMCPTools(...allTools);
99
+ * return <div>...</div>;
100
+ * }
101
+ * ```
102
+ *
103
+ * @param tools - Tools to register (variadic or array), optionally followed by an options object
104
+ */
105
+ export function useMCPTools(
106
+ ...tools: (RegisterableTool | RegisterableTool[])[]
107
+ ): void;
108
+
109
+ export function useMCPTools(
110
+ mcpWeb: MCPWeb,
111
+ ...tools: (RegisterableTool | RegisterableTool[])[]
112
+ ): void;
113
+
114
+ export function useMCPTools(
115
+ ...args: [...(RegisterableTool | RegisterableTool[])[], UseMCPToolsOptions]
116
+ ): void;
117
+
118
+ export function useMCPTools(
119
+ mcpWeb: MCPWeb,
120
+ ...args: [...(RegisterableTool | RegisterableTool[])[], UseMCPToolsOptions]
121
+ ): void;
122
+
123
+ // biome-ignore lint/suspicious/noExplicitAny: Implementation signature must be broad to support all overloads
124
+ export function useMCPTools(...allRawArgs: any[]): void {
125
+ const firstArg = allRawArgs[0];
126
+ const rest = allRawArgs.slice(1);
127
+ // Determine if first arg is MCPWeb instance or a tool
128
+ const isMCPWebInstance = (value: unknown): value is MCPWeb =>
129
+ typeof value === 'object' &&
130
+ value !== null &&
131
+ 'addTool' in value &&
132
+ 'addStateTools' in value &&
133
+ typeof (value as MCPWeb).addTool === 'function';
134
+
135
+ const isOptions = (value: unknown): value is UseMCPToolsOptions =>
136
+ typeof value === 'object' &&
137
+ value !== null &&
138
+ !Array.isArray(value) &&
139
+ !isMCPWebInstance(value) &&
140
+ !isCreatedTool(value) &&
141
+ !isCreatedStateTools(value) &&
142
+ !('handler' in value) &&
143
+ 'onRegistrationError' in value;
144
+
145
+ let mcpWebProp: MCPWeb | undefined;
146
+ let toolsToRegister: RegisterableTool[];
147
+ let options: UseMCPToolsOptions | undefined;
148
+
149
+ // Extract options from the last argument if present
150
+ const allArgs = [firstArg, ...rest];
151
+ const lastArg = allArgs[allArgs.length - 1];
152
+ if (isOptions(lastArg)) {
153
+ options = lastArg;
154
+ allArgs.pop();
155
+ }
156
+
157
+ if (isMCPWebInstance(allArgs[0])) {
158
+ mcpWebProp = allArgs[0];
159
+ toolsToRegister = (allArgs.slice(1) as (RegisterableTool | RegisterableTool[])[]).flat();
160
+ } else {
161
+ mcpWebProp = undefined;
162
+ toolsToRegister = (allArgs as (RegisterableTool | RegisterableTool[])[]).flat();
163
+ }
164
+
165
+ // Try to get from context if not provided
166
+ const context = useContext(MCPWebContext);
167
+ const mcpWeb = mcpWebProp ?? context?.mcpWeb;
168
+
169
+ if (!mcpWeb) {
170
+ throw new Error(
171
+ 'useMCPTools requires either mcpWeb as first argument or MCPWebProvider in component tree'
172
+ );
173
+ }
174
+
175
+ // Keep a stable reference to the tools array
176
+ const toolsRef = useRef(toolsToRegister);
177
+ toolsRef.current = toolsToRegister;
178
+
179
+ // Keep a stable reference to mcpWeb
180
+ const mcpWebRef = useRef(mcpWeb);
181
+ mcpWebRef.current = mcpWeb;
182
+
183
+ // Keep a stable reference to options
184
+ const optionsRef = useRef(options);
185
+ optionsRef.current = options;
186
+
187
+ useEffect(() => {
188
+ const cleanupFns: (() => void)[] = [];
189
+ const mcp = mcpWebRef.current;
190
+ const opts = optionsRef.current;
191
+ const addToolOptions = opts?.onRegistrationError
192
+ ? { onRegistrationError: opts.onRegistrationError }
193
+ : undefined;
194
+
195
+ for (const tool of toolsRef.current) {
196
+ if (isCreatedTool(tool)) {
197
+ // Register CreatedTool
198
+ mcp.addTool(tool, addToolOptions);
199
+ cleanupFns.push(() => mcp.removeTool(tool.definition.name));
200
+ } else if (isCreatedStateTools(tool)) {
201
+ // Register CreatedStateTools
202
+ const [, , cleanup] = mcp.addStateTools(tool);
203
+ cleanupFns.push(cleanup);
204
+ } else {
205
+ // Register raw ToolDefinition
206
+ // biome-ignore lint/suspicious/noExplicitAny: Internal addTool accepts ToolDefinition, but overloads are stricter
207
+ mcp.addTool(tool as any, addToolOptions);
208
+ cleanupFns.push(() => mcp.removeTool(tool.name));
209
+ }
210
+ }
211
+
212
+ return () => {
213
+ for (const cleanup of cleanupFns) {
214
+ cleanup();
215
+ }
216
+ };
217
+ }, []);
218
+ // Note: Empty deps because we use refs - tools are registered once on mount
219
+ // and cleaned up on unmount. If you need to re-register on tool change,
220
+ // use a key prop on the component.
221
+ }
@@ -0,0 +1,25 @@
1
+ import { useContext } from "react";
2
+ import { MCPWebContext, type MCPWebContextValue } from "./mcp-web-context";
3
+
4
+ /**
5
+ * Hook for accessing MCPWeb instance from context.
6
+ * Must be used within MCPWebProvider.
7
+ *
8
+ * @returns Object containing the MCPWeb instance and connection state
9
+ * @throws Error if used outside of MCPWebProvider
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * function MyComponent() {
14
+ * const { mcpWeb, isConnected } = useMCPWeb();
15
+ * // Use mcpWeb...
16
+ * }
17
+ * ```
18
+ */
19
+ export function useMCPWeb(): MCPWebContextValue {
20
+ const context = useContext(MCPWebContext);
21
+ if (!context) {
22
+ throw new Error('useMCPWeb must be used within MCPWebProvider');
23
+ }
24
+ return context;
25
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "declaration": true,
6
+ "declarationMap": true
7
+ },
8
+ "include": ["src/**/*"],
9
+ "exclude": ["dist", "node_modules"]
10
+ }