@tambo-ai/react 0.58.1 → 0.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks/react-query-hooks.d.ts +14 -1
- package/dist/hooks/react-query-hooks.d.ts.map +1 -1
- package/dist/hooks/react-query-hooks.js +13 -0
- package/dist/hooks/react-query-hooks.js.map +1 -1
- package/dist/hooks/use-tambo-stream-status.js +1 -1
- package/dist/hooks/use-tambo-stream-status.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/__tests__/elicitation.test.d.ts +2 -0
- package/dist/mcp/__tests__/elicitation.test.d.ts.map +1 -0
- package/dist/mcp/__tests__/elicitation.test.js +261 -0
- package/dist/mcp/__tests__/elicitation.test.js.map +1 -0
- package/dist/mcp/__tests__/mcp-client.test.js +0 -266
- package/dist/mcp/__tests__/mcp-client.test.js.map +1 -1
- package/dist/mcp/__tests__/mcp-hooks.test.d.ts +2 -0
- package/dist/mcp/__tests__/mcp-hooks.test.d.ts.map +1 -0
- package/dist/mcp/__tests__/mcp-hooks.test.js +504 -0
- package/dist/mcp/__tests__/mcp-hooks.test.js.map +1 -0
- package/dist/mcp/__tests__/tambo-mcp-provider.test.js +361 -16
- package/dist/mcp/__tests__/tambo-mcp-provider.test.js.map +1 -1
- package/dist/mcp/__tests__/use-mcp-servers.test.js +34 -9
- package/dist/mcp/__tests__/use-mcp-servers.test.js.map +1 -1
- package/dist/mcp/elicitation.d.ts +80 -0
- package/dist/mcp/elicitation.d.ts.map +1 -0
- package/dist/mcp/elicitation.js +55 -0
- package/dist/mcp/elicitation.js.map +1 -0
- package/dist/mcp/index.d.ts +4 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +5 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/mcp-client.d.ts +51 -86
- package/dist/mcp/mcp-client.d.ts.map +1 -1
- package/dist/mcp/mcp-client.js +22 -159
- package/dist/mcp/mcp-client.js.map +1 -1
- package/dist/mcp/mcp-hooks.d.ts +107 -0
- package/dist/mcp/mcp-hooks.d.ts.map +1 -0
- package/dist/mcp/mcp-hooks.js +103 -0
- package/dist/mcp/mcp-hooks.js.map +1 -0
- package/dist/mcp/tambo-mcp-provider.d.ts +86 -4
- package/dist/mcp/tambo-mcp-provider.d.ts.map +1 -1
- package/dist/mcp/tambo-mcp-provider.js +275 -106
- package/dist/mcp/tambo-mcp-provider.js.map +1 -1
- package/dist/providers/__tests__/tambo-thread-provider-initial-messages.test.js +3 -1
- package/dist/providers/__tests__/tambo-thread-provider-initial-messages.test.js.map +1 -1
- package/dist/providers/__tests__/tambo-thread-provider.test.js +25 -12
- package/dist/providers/__tests__/tambo-thread-provider.test.js.map +1 -1
- package/dist/providers/tambo-interactable-provider.d.ts.map +1 -1
- package/dist/providers/tambo-interactable-provider.js +11 -4
- package/dist/providers/tambo-interactable-provider.js.map +1 -1
- package/dist/providers/tambo-mcp-token-provider.d.ts +34 -0
- package/dist/providers/tambo-mcp-token-provider.d.ts.map +1 -0
- package/dist/providers/tambo-mcp-token-provider.js +69 -0
- package/dist/providers/tambo-mcp-token-provider.js.map +1 -0
- package/dist/providers/tambo-provider.d.ts.map +1 -1
- package/dist/providers/tambo-provider.js +7 -9
- package/dist/providers/tambo-provider.js.map +1 -1
- package/dist/providers/tambo-thread-provider.d.ts.map +1 -1
- package/dist/providers/tambo-thread-provider.js +14 -0
- package/dist/providers/tambo-thread-provider.js.map +1 -1
- package/esm/hooks/react-query-hooks.d.ts +14 -1
- package/esm/hooks/react-query-hooks.d.ts.map +1 -1
- package/esm/hooks/react-query-hooks.js +13 -1
- package/esm/hooks/react-query-hooks.js.map +1 -1
- package/esm/hooks/use-tambo-stream-status.js +1 -1
- package/esm/hooks/use-tambo-stream-status.js.map +1 -1
- package/esm/index.js +2 -0
- package/esm/index.js.map +1 -1
- package/esm/mcp/__tests__/elicitation.test.d.ts +2 -0
- package/esm/mcp/__tests__/elicitation.test.d.ts.map +1 -0
- package/esm/mcp/__tests__/elicitation.test.js +259 -0
- package/esm/mcp/__tests__/elicitation.test.js.map +1 -0
- package/esm/mcp/__tests__/mcp-client.test.js +0 -266
- package/esm/mcp/__tests__/mcp-client.test.js.map +1 -1
- package/esm/mcp/__tests__/mcp-hooks.test.d.ts +2 -0
- package/esm/mcp/__tests__/mcp-hooks.test.d.ts.map +1 -0
- package/esm/mcp/__tests__/mcp-hooks.test.js +469 -0
- package/esm/mcp/__tests__/mcp-hooks.test.js.map +1 -0
- package/esm/mcp/__tests__/tambo-mcp-provider.test.js +361 -16
- package/esm/mcp/__tests__/tambo-mcp-provider.test.js.map +1 -1
- package/esm/mcp/__tests__/use-mcp-servers.test.js +34 -9
- package/esm/mcp/__tests__/use-mcp-servers.test.js.map +1 -1
- package/esm/mcp/elicitation.d.ts +80 -0
- package/esm/mcp/elicitation.d.ts.map +1 -0
- package/esm/mcp/elicitation.js +52 -0
- package/esm/mcp/elicitation.js.map +1 -0
- package/esm/mcp/index.d.ts +4 -1
- package/esm/mcp/index.d.ts.map +1 -1
- package/esm/mcp/index.js +2 -1
- package/esm/mcp/index.js.map +1 -1
- package/esm/mcp/mcp-client.d.ts +51 -86
- package/esm/mcp/mcp-client.d.ts.map +1 -1
- package/esm/mcp/mcp-client.js +22 -159
- package/esm/mcp/mcp-client.js.map +1 -1
- package/esm/mcp/mcp-hooks.d.ts +107 -0
- package/esm/mcp/mcp-hooks.d.ts.map +1 -0
- package/esm/mcp/mcp-hooks.js +99 -0
- package/esm/mcp/mcp-hooks.js.map +1 -0
- package/esm/mcp/tambo-mcp-provider.d.ts +86 -4
- package/esm/mcp/tambo-mcp-provider.d.ts.map +1 -1
- package/esm/mcp/tambo-mcp-provider.js +275 -107
- package/esm/mcp/tambo-mcp-provider.js.map +1 -1
- package/esm/providers/__tests__/tambo-thread-provider-initial-messages.test.js +3 -1
- package/esm/providers/__tests__/tambo-thread-provider-initial-messages.test.js.map +1 -1
- package/esm/providers/__tests__/tambo-thread-provider.test.js +25 -12
- package/esm/providers/__tests__/tambo-thread-provider.test.js.map +1 -1
- package/esm/providers/tambo-interactable-provider.d.ts.map +1 -1
- package/esm/providers/tambo-interactable-provider.js +11 -4
- package/esm/providers/tambo-interactable-provider.js.map +1 -1
- package/esm/providers/tambo-mcp-token-provider.d.ts +34 -0
- package/esm/providers/tambo-mcp-token-provider.d.ts.map +1 -0
- package/esm/providers/tambo-mcp-token-provider.js +31 -0
- package/esm/providers/tambo-mcp-token-provider.js.map +1 -0
- package/esm/providers/tambo-provider.d.ts.map +1 -1
- package/esm/providers/tambo-provider.js +7 -9
- package/esm/providers/tambo-provider.js.map +1 -1
- package/esm/providers/tambo-thread-provider.d.ts.map +1 -1
- package/esm/providers/tambo-thread-provider.js +14 -0
- package/esm/providers/tambo-thread-provider.js.map +1 -1
- package/package.json +8 -8
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { FC } from "react";
|
|
2
|
-
import { MCPClient, MCPTransport } from "./mcp-client";
|
|
2
|
+
import { MCPClient, MCPElicitationHandler, MCPHandlers, MCPSamplingHandler, MCPTransport } from "./mcp-client";
|
|
3
3
|
/**
|
|
4
4
|
* Extracts error message from MCP tool result content.
|
|
5
5
|
* Handles both array and string content formats.
|
|
@@ -7,27 +7,79 @@ import { MCPClient, MCPTransport } from "./mcp-client";
|
|
|
7
7
|
* @returns The extracted error message as a string
|
|
8
8
|
*/
|
|
9
9
|
export declare function extractErrorMessage(content: unknown): string;
|
|
10
|
+
/**
|
|
11
|
+
* User-provided configuration for an MCP server.
|
|
12
|
+
*/
|
|
10
13
|
export interface McpServerInfo {
|
|
14
|
+
/** Optional name for the MCP server */
|
|
11
15
|
name?: string;
|
|
16
|
+
/** The URL of the MCP server to connect to */
|
|
12
17
|
url: string;
|
|
18
|
+
/** Optional description of the MCP server */
|
|
13
19
|
description?: string;
|
|
20
|
+
/** The transport type to use (SSE or HTTP). Defaults to SSE for string URLs */
|
|
14
21
|
transport?: MCPTransport;
|
|
22
|
+
/** Optional custom headers to include in requests */
|
|
15
23
|
customHeaders?: Record<string, string>;
|
|
24
|
+
/**
|
|
25
|
+
* Optional handlers for elicitation and sampling requests from the server.
|
|
26
|
+
* Note: These callbacks should be stable (e.g., wrapped in useCallback or defined outside the component)
|
|
27
|
+
* to avoid constant re-registration of the MCP server on every render.
|
|
28
|
+
*/
|
|
29
|
+
handlers?: Partial<MCPHandlers>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Normalized server information with a stable derived key.
|
|
33
|
+
*/
|
|
34
|
+
interface McpServerConfig extends McpServerInfo {
|
|
35
|
+
/**
|
|
36
|
+
* Stable identity for this server derived from its URL/transport/headers.
|
|
37
|
+
* Present for all server states (connected or failed).
|
|
38
|
+
*/
|
|
39
|
+
key: string;
|
|
16
40
|
}
|
|
17
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Connected MCP server with an active client.
|
|
43
|
+
*/
|
|
44
|
+
export interface ConnectedMcpServer extends McpServerConfig {
|
|
18
45
|
client: MCPClient;
|
|
19
46
|
}
|
|
20
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Failed MCP server with a connection error.
|
|
49
|
+
*/
|
|
50
|
+
export interface FailedMcpServer extends McpServerConfig {
|
|
21
51
|
client?: never;
|
|
22
52
|
connectionError: Error;
|
|
23
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* An active or failed MCP server, with access to the MCP client.
|
|
56
|
+
*/
|
|
24
57
|
export type McpServer = ConnectedMcpServer | FailedMcpServer;
|
|
58
|
+
/**
|
|
59
|
+
* Provider-level MCP handlers that receive the McpServerInfo as context in addition to the request.
|
|
60
|
+
* These handlers are applied to all MCP servers unless overridden by per-server handlers.
|
|
61
|
+
*
|
|
62
|
+
* Handlers receive three parameters:
|
|
63
|
+
* 1. request - The MCP request
|
|
64
|
+
* 2. extra - RequestHandlerExtra containing AbortSignal and other metadata
|
|
65
|
+
* 3. serverInfo - Configuration of the MCP server that triggered this request
|
|
66
|
+
*/
|
|
67
|
+
export interface ProviderMCPHandlers {
|
|
68
|
+
elicitation?: (request: Parameters<MCPElicitationHandler>[0], extra: Parameters<MCPElicitationHandler>[1], serverInfo: McpServerConfig) => ReturnType<MCPElicitationHandler>;
|
|
69
|
+
sampling?: (request: Parameters<MCPSamplingHandler>[0], extra: Parameters<MCPSamplingHandler>[1], serverInfo: McpServerConfig) => ReturnType<MCPSamplingHandler>;
|
|
70
|
+
}
|
|
25
71
|
/**
|
|
26
72
|
* This provider is used to register tools from MCP servers.
|
|
27
|
-
*
|
|
73
|
+
* It automatically includes an internal Tambo MCP server when an MCP access token is available.
|
|
74
|
+
* @param props - The provider props
|
|
75
|
+
* @param props.mcpServers - Array of MCP server configurations
|
|
76
|
+
* @param props.handlers - Optional handlers applied to all MCP servers unless overridden per-server
|
|
77
|
+
* @param props.children - The children to wrap
|
|
78
|
+
* @returns The TamboMcpProvider component
|
|
28
79
|
*/
|
|
29
80
|
export declare const TamboMcpProvider: FC<{
|
|
30
81
|
mcpServers: (McpServerInfo | string)[];
|
|
82
|
+
handlers?: ProviderMCPHandlers;
|
|
31
83
|
children: React.ReactNode;
|
|
32
84
|
}>;
|
|
33
85
|
/**
|
|
@@ -54,4 +106,34 @@ export declare const TamboMcpProvider: FC<{
|
|
|
54
106
|
* @returns The MCP servers
|
|
55
107
|
*/
|
|
56
108
|
export declare const useTamboMcpServers: () => McpServer[];
|
|
109
|
+
/**
|
|
110
|
+
* Hook to access elicitation context from TamboMcpProvider.
|
|
111
|
+
* This provides access to the current elicitation request and methods to respond to it.
|
|
112
|
+
*
|
|
113
|
+
* The elicitation state is automatically managed by TamboMcpProvider when MCP servers
|
|
114
|
+
* request user input through the elicitation protocol.
|
|
115
|
+
* @returns The elicitation context with current request and response handlers
|
|
116
|
+
* @example
|
|
117
|
+
* ```tsx
|
|
118
|
+
* function ElicitationUI() {
|
|
119
|
+
* const { elicitation, resolveElicitation } = useTamboElicitationContext();
|
|
120
|
+
*
|
|
121
|
+
* if (!elicitation) return null;
|
|
122
|
+
*
|
|
123
|
+
* return (
|
|
124
|
+
* <div>
|
|
125
|
+
* <p>{elicitation.message}</p>
|
|
126
|
+
* <button onClick={() => resolveElicitation?.({ action: "accept", content: {} })}>
|
|
127
|
+
* Accept
|
|
128
|
+
* </button>
|
|
129
|
+
* </div>
|
|
130
|
+
* );
|
|
131
|
+
* }
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export declare const useTamboElicitationContext: () => {
|
|
135
|
+
elicitation: import("./elicitation").TamboElicitationRequest | null;
|
|
136
|
+
resolveElicitation: ((response: import("./elicitation").TamboElicitationResponse) => void) | null;
|
|
137
|
+
};
|
|
138
|
+
export {};
|
|
57
139
|
//# sourceMappingURL=tambo-mcp-provider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tambo-mcp-provider.d.ts","sourceRoot":"","sources":["../../src/mcp/tambo-mcp-provider.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tambo-mcp-provider.d.ts","sourceRoot":"","sources":["../../src/mcp/tambo-mcp-provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAEZ,EAAE,EAMH,MAAM,OAAO,CAAC;AAMf,OAAO,EACL,SAAS,EACT,qBAAqB,EACrB,WAAW,EACX,kBAAkB,EAClB,YAAY,EACb,MAAM,cAAc,CAAC;AAEtB;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAoB5D;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,qDAAqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;CACjC;AAED;;GAEG;AACH,UAAU,eAAgB,SAAQ,aAAa;IAC7C;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,eAAe;IACzD,MAAM,EAAE,SAAS,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,eAAe,EAAE,KAAK,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,kBAAkB,GAAG,eAAe,CAAC;AAE7D;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,CACZ,OAAO,EAAE,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,EAC7C,KAAK,EAAE,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,EAC3C,UAAU,EAAE,eAAe,KACxB,UAAU,CAAC,qBAAqB,CAAC,CAAC;IACvC,QAAQ,CAAC,EAAE,CACT,OAAO,EAAE,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,EAC1C,KAAK,EAAE,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,EACxC,UAAU,EAAE,eAAe,KACxB,UAAU,CAAC,kBAAkB,CAAC,CAAC;CACrC;AAkBD;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC;IAChC,UAAU,EAAE,CAAC,aAAa,GAAG,MAAM,CAAC,EAAE,CAAC;IACvC,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CA2RA,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,kBAAkB,mBAE9B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,0BAA0B;;;CAMtC,CAAC"}
|
|
@@ -33,12 +33,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.useTamboMcpServers = exports.TamboMcpProvider = void 0;
|
|
36
|
+
exports.useTamboElicitationContext = exports.useTamboMcpServers = exports.TamboMcpProvider = void 0;
|
|
37
37
|
exports.extractErrorMessage = extractErrorMessage;
|
|
38
|
-
const fast_equals_1 = require("fast-equals");
|
|
39
38
|
const react_1 = __importStar(require("react"));
|
|
39
|
+
const tambo_mcp_token_provider_1 = require("../providers/tambo-mcp-token-provider");
|
|
40
40
|
const tambo_registry_provider_1 = require("../providers/tambo-registry-provider");
|
|
41
41
|
const content_parts_1 = require("../util/content-parts");
|
|
42
|
+
const elicitation_1 = require("./elicitation");
|
|
42
43
|
const mcp_client_1 = require("./mcp-client");
|
|
43
44
|
/**
|
|
44
45
|
* Extracts error message from MCP tool result content.
|
|
@@ -52,7 +53,7 @@ function extractErrorMessage(content) {
|
|
|
52
53
|
}
|
|
53
54
|
if (Array.isArray(content)) {
|
|
54
55
|
const textItems = content
|
|
55
|
-
.filter((item) => item
|
|
56
|
+
.filter((item) => item?.type === "text" && typeof item.text === "string")
|
|
56
57
|
.map((item) => item.text);
|
|
57
58
|
return textItems.length > 0
|
|
58
59
|
? textItems.join(" ")
|
|
@@ -63,120 +64,236 @@ function extractErrorMessage(content) {
|
|
|
63
64
|
}
|
|
64
65
|
return `${content}`;
|
|
65
66
|
}
|
|
66
|
-
const McpProviderContext = (0, react_1.createContext)(
|
|
67
|
+
const McpProviderContext = (0, react_1.createContext)({
|
|
68
|
+
servers: [],
|
|
69
|
+
elicitation: null,
|
|
70
|
+
resolveElicitation: null,
|
|
71
|
+
});
|
|
72
|
+
// Constant for the internal Tambo MCP server name
|
|
73
|
+
const TAMBO_INTERNAL_MCP_SERVER_NAME = "__tambo_internal_mcp_server__";
|
|
67
74
|
/**
|
|
68
75
|
* This provider is used to register tools from MCP servers.
|
|
69
|
-
*
|
|
76
|
+
* It automatically includes an internal Tambo MCP server when an MCP access token is available.
|
|
77
|
+
* @param props - The provider props
|
|
78
|
+
* @param props.mcpServers - Array of MCP server configurations
|
|
79
|
+
* @param props.handlers - Optional handlers applied to all MCP servers unless overridden per-server
|
|
80
|
+
* @param props.children - The children to wrap
|
|
81
|
+
* @returns The TamboMcpProvider component
|
|
70
82
|
*/
|
|
71
|
-
const TamboMcpProvider = ({ mcpServers, children }) => {
|
|
83
|
+
const TamboMcpProvider = ({ mcpServers, handlers, children }) => {
|
|
72
84
|
const { registerTool } = (0, tambo_registry_provider_1.useTamboRegistry)();
|
|
85
|
+
const { mcpAccessToken, tamboBaseUrl } = (0, tambo_mcp_token_provider_1.useTamboMcpToken)();
|
|
86
|
+
const providerSamplingHandler = handlers?.sampling;
|
|
87
|
+
// Elicitation state and default handler
|
|
88
|
+
const { elicitation, resolveElicitation, defaultElicitationHandler } = (0, elicitation_1.useElicitation)();
|
|
89
|
+
// Use provided handler or fall back to default
|
|
90
|
+
const providerElicitationHandler = handlers?.elicitation ?? defaultElicitationHandler;
|
|
91
|
+
// Stable reference to track active clients by server key
|
|
92
|
+
const clientMapRef = (0, react_1.useRef)(new Map());
|
|
93
|
+
// Track tool ownership to prevent duplicate registrations across servers
|
|
94
|
+
// toolOwnerRef: tool name -> server key; keyToToolsRef: server key -> set of tool names
|
|
95
|
+
const toolOwnerRef = (0, react_1.useRef)(new Map());
|
|
96
|
+
const keyToToolsRef = (0, react_1.useRef)(new Map());
|
|
97
|
+
// State for exposing connected servers to consumers
|
|
73
98
|
const [connectedMcpServers, setConnectedMcpServers] = (0, react_1.useState)([]);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
99
|
+
// Stable map of current server configurations keyed by server key
|
|
100
|
+
const currentServersMap = (0, react_1.useMemo)(() => {
|
|
101
|
+
const servers = [...mcpServers];
|
|
102
|
+
// Add internal Tambo MCP server if we have an access token and a base URL
|
|
103
|
+
if (mcpAccessToken && tamboBaseUrl) {
|
|
104
|
+
const base = new URL(tamboBaseUrl);
|
|
105
|
+
base.pathname = `${base.pathname.replace(/\/+$/, "")}/mcp`;
|
|
106
|
+
const tamboMcpUrl = base.toString();
|
|
107
|
+
servers.push({
|
|
108
|
+
name: TAMBO_INTERNAL_MCP_SERVER_NAME,
|
|
109
|
+
url: tamboMcpUrl,
|
|
110
|
+
transport: mcp_client_1.MCPTransport.HTTP,
|
|
111
|
+
customHeaders: {
|
|
112
|
+
Authorization: `Bearer ${mcpAccessToken}`,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
77
115
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
116
|
+
// Create a map of server key -> server info for efficient lookups
|
|
117
|
+
const serverMap = new Map();
|
|
118
|
+
servers.forEach((server) => {
|
|
119
|
+
const serverInfo = normalizeServerInfo(server);
|
|
120
|
+
// Store without cloning to avoid unnecessary allocation
|
|
121
|
+
serverMap.set(serverInfo.key, serverInfo);
|
|
122
|
+
});
|
|
123
|
+
return serverMap;
|
|
124
|
+
}, [mcpServers, mcpAccessToken, tamboBaseUrl]);
|
|
125
|
+
// Main effect: manage client lifecycle (create/remove)
|
|
126
|
+
(0, react_1.useEffect)(() => {
|
|
127
|
+
const clientMap = clientMapRef.current;
|
|
128
|
+
const currentKeys = new Set(currentServersMap.keys());
|
|
129
|
+
const existingKeys = new Set(clientMap.keys());
|
|
130
|
+
// 1. Remove clients that are no longer in the current server list
|
|
131
|
+
const keysToRemove = Array.from(existingKeys).filter((key) => !currentKeys.has(key));
|
|
132
|
+
keysToRemove.forEach((key) => {
|
|
133
|
+
const server = clientMap.get(key);
|
|
134
|
+
if (server?.client) {
|
|
86
135
|
try {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
136
|
+
server.client.close();
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
// Avoid logging sensitive data embedded in the key (headers)
|
|
140
|
+
const url = server.url ?? "(unknown url)";
|
|
141
|
+
console.error(`Error closing MCP client for ${url}:`, error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Release tool ownership for this server
|
|
145
|
+
const owned = keyToToolsRef.current.get(key);
|
|
146
|
+
if (owned) {
|
|
147
|
+
for (const name of owned)
|
|
148
|
+
toolOwnerRef.current.delete(name);
|
|
149
|
+
keyToToolsRef.current.delete(key);
|
|
150
|
+
}
|
|
151
|
+
clientMap.delete(key);
|
|
152
|
+
});
|
|
153
|
+
// 2. Add new clients for servers that don't exist yet
|
|
154
|
+
const keysToAdd = Array.from(currentKeys).filter((key) => !existingKeys.has(key));
|
|
155
|
+
if (keysToAdd.length > 0) {
|
|
156
|
+
// Async initialization of new clients
|
|
157
|
+
Promise.allSettled(keysToAdd.map(async (key) => {
|
|
158
|
+
const serverInfo = currentServersMap.get(key);
|
|
159
|
+
try {
|
|
160
|
+
// Build effective handlers (per-server overrides provider)
|
|
161
|
+
const effectiveHandlers = {};
|
|
162
|
+
if (serverInfo.handlers?.elicitation) {
|
|
163
|
+
effectiveHandlers.elicitation = serverInfo.handlers.elicitation;
|
|
164
|
+
}
|
|
165
|
+
else if (providerElicitationHandler) {
|
|
166
|
+
effectiveHandlers.elicitation = async (request, extra) => await providerElicitationHandler(request, extra, serverInfo);
|
|
167
|
+
}
|
|
168
|
+
if (serverInfo.handlers?.sampling) {
|
|
169
|
+
effectiveHandlers.sampling = serverInfo.handlers.sampling;
|
|
170
|
+
}
|
|
171
|
+
else if (providerSamplingHandler) {
|
|
172
|
+
effectiveHandlers.sampling = async (request, extra) => await providerSamplingHandler(request, extra, serverInfo);
|
|
173
|
+
}
|
|
174
|
+
const client = await mcp_client_1.MCPClient.create(serverInfo.url, serverInfo.transport, serverInfo.customHeaders, undefined, undefined, effectiveHandlers);
|
|
175
|
+
const connectedServer = {
|
|
176
|
+
...serverInfo,
|
|
177
|
+
client,
|
|
92
178
|
};
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
179
|
+
clientMap.set(key, connectedServer);
|
|
180
|
+
setConnectedMcpServers(Array.from(clientMap.values()));
|
|
181
|
+
// Register tools from this server (deduplicated by ownership)
|
|
182
|
+
try {
|
|
183
|
+
const tools = await client.listTools();
|
|
184
|
+
tools.forEach((tool) => {
|
|
185
|
+
// Skip if another server already owns this tool
|
|
186
|
+
const currentOwner = toolOwnerRef.current.get(tool.name);
|
|
187
|
+
if (currentOwner && currentOwner !== key) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// Record ownership for this server key
|
|
191
|
+
if (!currentOwner) {
|
|
192
|
+
toolOwnerRef.current.set(tool.name, key);
|
|
193
|
+
if (!keyToToolsRef.current.has(key)) {
|
|
194
|
+
keyToToolsRef.current.set(key, new Set());
|
|
195
|
+
}
|
|
196
|
+
keyToToolsRef.current.get(key).add(tool.name);
|
|
197
|
+
}
|
|
198
|
+
registerTool({
|
|
199
|
+
description: tool.description ?? "",
|
|
200
|
+
name: tool.name,
|
|
201
|
+
tool: async (args = {}) => {
|
|
202
|
+
const server = clientMap.get(key);
|
|
203
|
+
if (!server?.client) {
|
|
204
|
+
throw new Error(`MCP server for tool ${tool.name} is not connected`);
|
|
205
|
+
}
|
|
206
|
+
const result = await server.client.callTool(tool.name, args);
|
|
207
|
+
if (result.isError) {
|
|
208
|
+
const errorMessage = extractErrorMessage(result.content);
|
|
209
|
+
throw new Error(errorMessage);
|
|
210
|
+
}
|
|
211
|
+
return result.content;
|
|
212
|
+
},
|
|
213
|
+
toolSchema: tool.inputSchema,
|
|
214
|
+
transformToContent: (content) => {
|
|
215
|
+
if ((0, content_parts_1.isContentPartArray)(content)) {
|
|
216
|
+
return content;
|
|
217
|
+
}
|
|
218
|
+
return [{ type: "text", text: (0, content_parts_1.toText)(content) }];
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
console.error(`Failed to register tools from MCP server ${serverInfo.url}:`, error);
|
|
225
|
+
}
|
|
101
226
|
}
|
|
102
227
|
catch (error) {
|
|
103
|
-
const
|
|
104
|
-
...
|
|
228
|
+
const failedServer = {
|
|
229
|
+
...serverInfo,
|
|
105
230
|
connectionError: error,
|
|
106
231
|
};
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// replace the server if it already exists
|
|
111
|
-
...prev.filter((s) => !equalsMcpServer(s, mcpServerInfo)),
|
|
112
|
-
failedMcpServer,
|
|
113
|
-
]);
|
|
114
|
-
return failedMcpServer;
|
|
232
|
+
clientMap.set(key, failedServer);
|
|
233
|
+
setConnectedMcpServers(Array.from(clientMap.values()));
|
|
234
|
+
console.error(`Failed to connect to MCP server ${serverInfo.url}:`, error);
|
|
115
235
|
}
|
|
116
236
|
}));
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
if (
|
|
133
|
-
|
|
237
|
+
}
|
|
238
|
+
// Update state after removals (additions update state asynchronously above)
|
|
239
|
+
if (keysToRemove.length > 0) {
|
|
240
|
+
setConnectedMcpServers(Array.from(clientMap.values()));
|
|
241
|
+
}
|
|
242
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
243
|
+
}, [currentServersMap, registerTool]);
|
|
244
|
+
// Update handlers when they change (without recreating clients)
|
|
245
|
+
(0, react_1.useEffect)(() => {
|
|
246
|
+
const clientMap = clientMapRef.current;
|
|
247
|
+
clientMap.forEach((server, key) => {
|
|
248
|
+
if (!server.client) {
|
|
249
|
+
return; // Skip failed servers
|
|
250
|
+
}
|
|
251
|
+
const serverInfo = currentServersMap.get(key);
|
|
252
|
+
if (!serverInfo) {
|
|
253
|
+
return; // Server was removed, handled by main effect
|
|
134
254
|
}
|
|
135
|
-
//
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if ((0, content_parts_1.isContentPartArray)(content)) {
|
|
166
|
-
return content;
|
|
167
|
-
}
|
|
168
|
-
return [{ type: "text", text: (0, content_parts_1.toText)(content) }];
|
|
169
|
-
},
|
|
170
|
-
});
|
|
255
|
+
// Determine effective handlers
|
|
256
|
+
const effectiveElicitationHandler = serverInfo.handlers?.elicitation ??
|
|
257
|
+
(providerElicitationHandler
|
|
258
|
+
? async (request, extra) => await providerElicitationHandler(request, extra, serverInfo)
|
|
259
|
+
: undefined);
|
|
260
|
+
const effectiveSamplingHandler = serverInfo.handlers?.sampling ??
|
|
261
|
+
(providerSamplingHandler
|
|
262
|
+
? async (request, extra) => await providerSamplingHandler(request, extra, serverInfo)
|
|
263
|
+
: undefined);
|
|
264
|
+
// Update handlers unconditionally (allows removal by passing undefined)
|
|
265
|
+
server.client.updateElicitationHandler?.(effectiveElicitationHandler);
|
|
266
|
+
server.client.updateSamplingHandler?.(effectiveSamplingHandler);
|
|
267
|
+
});
|
|
268
|
+
}, [currentServersMap, providerElicitationHandler, providerSamplingHandler]);
|
|
269
|
+
// Cleanup on unmount: close all clients
|
|
270
|
+
(0, react_1.useEffect)(() => {
|
|
271
|
+
const clientMap = clientMapRef.current;
|
|
272
|
+
const ownerMapAtMount = toolOwnerRef.current;
|
|
273
|
+
const keyToToolsAtMount = keyToToolsRef.current;
|
|
274
|
+
return () => {
|
|
275
|
+
clientMap.forEach((server, _key) => {
|
|
276
|
+
if (server.client) {
|
|
277
|
+
try {
|
|
278
|
+
server.client.close();
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
const url = server.url ?? "(unknown url)";
|
|
282
|
+
console.error(`Error closing MCP client on unmount for ${url}:`, error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
171
285
|
});
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
286
|
+
clientMap.clear();
|
|
287
|
+
ownerMapAtMount.clear();
|
|
288
|
+
keyToToolsAtMount.clear();
|
|
289
|
+
};
|
|
290
|
+
}, []);
|
|
291
|
+
const contextValue = (0, react_1.useMemo)(() => ({
|
|
292
|
+
servers: connectedMcpServers,
|
|
293
|
+
elicitation,
|
|
294
|
+
resolveElicitation,
|
|
295
|
+
}), [connectedMcpServers, elicitation, resolveElicitation]);
|
|
296
|
+
return (react_1.default.createElement(McpProviderContext.Provider, { value: contextValue }, children));
|
|
180
297
|
};
|
|
181
298
|
exports.TamboMcpProvider = TamboMcpProvider;
|
|
182
299
|
/**
|
|
@@ -203,12 +320,64 @@ exports.TamboMcpProvider = TamboMcpProvider;
|
|
|
203
320
|
* @returns The MCP servers
|
|
204
321
|
*/
|
|
205
322
|
const useTamboMcpServers = () => {
|
|
206
|
-
return (0, react_1.useContext)(McpProviderContext);
|
|
323
|
+
return (0, react_1.useContext)(McpProviderContext).servers;
|
|
207
324
|
};
|
|
208
325
|
exports.useTamboMcpServers = useTamboMcpServers;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
326
|
+
/**
|
|
327
|
+
* Hook to access elicitation context from TamboMcpProvider.
|
|
328
|
+
* This provides access to the current elicitation request and methods to respond to it.
|
|
329
|
+
*
|
|
330
|
+
* The elicitation state is automatically managed by TamboMcpProvider when MCP servers
|
|
331
|
+
* request user input through the elicitation protocol.
|
|
332
|
+
* @returns The elicitation context with current request and response handlers
|
|
333
|
+
* @example
|
|
334
|
+
* ```tsx
|
|
335
|
+
* function ElicitationUI() {
|
|
336
|
+
* const { elicitation, resolveElicitation } = useTamboElicitationContext();
|
|
337
|
+
*
|
|
338
|
+
* if (!elicitation) return null;
|
|
339
|
+
*
|
|
340
|
+
* return (
|
|
341
|
+
* <div>
|
|
342
|
+
* <p>{elicitation.message}</p>
|
|
343
|
+
* <button onClick={() => resolveElicitation?.({ action: "accept", content: {} })}>
|
|
344
|
+
* Accept
|
|
345
|
+
* </button>
|
|
346
|
+
* </div>
|
|
347
|
+
* );
|
|
348
|
+
* }
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
const useTamboElicitationContext = () => {
|
|
352
|
+
const context = (0, react_1.useContext)(McpProviderContext);
|
|
353
|
+
return {
|
|
354
|
+
elicitation: context.elicitation,
|
|
355
|
+
resolveElicitation: context.resolveElicitation,
|
|
356
|
+
};
|
|
357
|
+
};
|
|
358
|
+
exports.useTamboElicitationContext = useTamboElicitationContext;
|
|
359
|
+
/**
|
|
360
|
+
* Creates a stable identifier for an MCP server based on its connection properties.
|
|
361
|
+
* Two servers with the same URL, transport, and headers will have the same key.
|
|
362
|
+
* @returns A stable string key identifying the server
|
|
363
|
+
*/
|
|
364
|
+
function getServerKey(serverInfo) {
|
|
365
|
+
const headerStr = serverInfo.customHeaders
|
|
366
|
+
? JSON.stringify(Object.entries(serverInfo.customHeaders)
|
|
367
|
+
.map(([k, v]) => [k.toLowerCase(), v])
|
|
368
|
+
.sort(([a], [b]) => a.localeCompare(b)))
|
|
369
|
+
: "";
|
|
370
|
+
return `${serverInfo.url}|${serverInfo.transport ?? mcp_client_1.MCPTransport.SSE}|${headerStr}`;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Normalizes a server definition (string or object) into a McpServerInfo.
|
|
374
|
+
* @returns The normalized McpServerInfo object
|
|
375
|
+
*/
|
|
376
|
+
function normalizeServerInfo(server) {
|
|
377
|
+
const s = typeof server === "string"
|
|
378
|
+
? { url: server, transport: mcp_client_1.MCPTransport.SSE }
|
|
379
|
+
: server;
|
|
380
|
+
const key = getServerKey(s);
|
|
381
|
+
return { ...s, key };
|
|
213
382
|
}
|
|
214
383
|
//# sourceMappingURL=tambo-mcp-provider.js.map
|