@tambo-ai/react 0.53.2 → 0.54.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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-mcp-servers.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-mcp-servers.test.d.ts","sourceRoot":"","sources":["../../../src/mcp/__tests__/use-mcp-servers.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const react_1 = require("@testing-library/react");
37
+ const react_2 = __importStar(require("react"));
38
+ const tambo_registry_provider_1 = require("../../providers/tambo-registry-provider");
39
+ const tambo_mcp_provider_1 = require("../tambo-mcp-provider");
40
+ // Mock the registry to provide a no-op registerTool
41
+ // Do not mock the registry; use the real provider in render
42
+ // Mock the MCP client; use a mutable implementation to avoid TDZ issues
43
+ let createImpl = jest.fn();
44
+ jest.mock("../mcp-client", () => ({
45
+ MCPClient: { create: (...args) => createImpl(...args) },
46
+ MCPTransport: { SSE: "sse", HTTP: "http" },
47
+ }));
48
+ // Import after mocks note: jest.mock calls are hoisted, so standard imports are fine
49
+ describe("useTamboMcpServers + TamboMcpProvider", () => {
50
+ beforeEach(() => {
51
+ createImpl = jest.fn();
52
+ });
53
+ it("provides normalized MCP server entries to inner components", async () => {
54
+ const fakeClient = { listTools: jest.fn().mockResolvedValue([]) };
55
+ createImpl.mockResolvedValue(fakeClient);
56
+ const Inner = () => {
57
+ const servers = (0, tambo_mcp_provider_1.useTamboMcpServers)();
58
+ return (react_2.default.createElement("div", null,
59
+ react_2.default.createElement("div", { "data-testid": "count" }, servers.length),
60
+ react_2.default.createElement("div", { "data-testid": "urls" }, servers.map((s) => s.url).join(","))));
61
+ };
62
+ const { getByTestId } = (0, react_1.render)(react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, null,
63
+ react_2.default.createElement(tambo_mcp_provider_1.TamboMcpProvider, { mcpServers: [{ url: "https://one.example" }, "https://two.example"] },
64
+ react_2.default.createElement(Inner, null))));
65
+ await (0, react_1.waitFor)(() => {
66
+ expect(getByTestId("count").textContent).toBe("2");
67
+ const urls = getByTestId("urls").textContent || "";
68
+ expect(urls).toContain("https://one.example");
69
+ expect(urls).toContain("https://two.example");
70
+ });
71
+ });
72
+ it("marks a successfully connected server with a client instance", async () => {
73
+ const fakeClient = { listTools: jest.fn().mockResolvedValue([]) };
74
+ createImpl.mockResolvedValue(fakeClient);
75
+ let latest = [];
76
+ const Capture = () => {
77
+ const servers = (0, tambo_mcp_provider_1.useTamboMcpServers)();
78
+ (0, react_2.useEffect)(() => {
79
+ latest = servers;
80
+ }, [servers]);
81
+ return null;
82
+ };
83
+ (0, react_1.render)(react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, null,
84
+ react_2.default.createElement(tambo_mcp_provider_1.TamboMcpProvider, { mcpServers: [{ url: "https://ok.example" }] },
85
+ react_2.default.createElement(Capture, null))));
86
+ await (0, react_1.waitFor)(() => {
87
+ expect(latest.length).toBe(1);
88
+ expect("client" in latest[0]).toBe(true);
89
+ expect(latest[0].client).toBe(fakeClient);
90
+ // no connectionError on connected server
91
+ expect(latest[0].connectionError).toBeUndefined();
92
+ });
93
+ });
94
+ it("marks a failed server with a connectionError and no client", async () => {
95
+ const boom = new Error("boom");
96
+ createImpl.mockRejectedValue(boom);
97
+ let latest = [];
98
+ const Capture = () => {
99
+ const servers = (0, tambo_mcp_provider_1.useTamboMcpServers)();
100
+ (0, react_2.useEffect)(() => {
101
+ latest = servers;
102
+ }, [servers]);
103
+ return null;
104
+ };
105
+ (0, react_1.render)(react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, null,
106
+ react_2.default.createElement(tambo_mcp_provider_1.TamboMcpProvider, { mcpServers: [{ url: "https://fail.example" }] },
107
+ react_2.default.createElement(Capture, null))));
108
+ await (0, react_1.waitFor)(() => {
109
+ expect(latest.length).toBe(1);
110
+ expect("client" in latest[0]).toBe(false);
111
+ // @ts-expect-error narrowing at runtime
112
+ expect(latest[0].connectionError).toBeInstanceOf(Error);
113
+ // @ts-expect-error narrowing at runtime
114
+ expect(latest[0].connectionError?.message).toBe("boom");
115
+ });
116
+ });
117
+ });
118
+ //# sourceMappingURL=use-mcp-servers.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-mcp-servers.test.js","sourceRoot":"","sources":["../../../src/mcp/__tests__/use-mcp-servers.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import { render, waitFor } from \"@testing-library/react\";\nimport React, { useEffect } from \"react\";\nimport { TamboRegistryProvider } from \"../../providers/tambo-registry-provider\";\nimport {\n TamboMcpProvider,\n useTamboMcpServers,\n type McpServer,\n} from \"../tambo-mcp-provider\";\n\n// Mock the registry to provide a no-op registerTool\n// Do not mock the registry; use the real provider in render\n\n// Mock the MCP client; use a mutable implementation to avoid TDZ issues\nlet createImpl: jest.Mock<any, any> = jest.fn();\njest.mock(\"../mcp-client\", () => ({\n MCPClient: { create: (...args: any[]) => createImpl(...args) },\n MCPTransport: { SSE: \"sse\", HTTP: \"http\" },\n}));\n\n// Import after mocks note: jest.mock calls are hoisted, so standard imports are fine\n\ndescribe(\"useTamboMcpServers + TamboMcpProvider\", () => {\n beforeEach(() => {\n createImpl = jest.fn();\n });\n\n it(\"provides normalized MCP server entries to inner components\", async () => {\n const fakeClient = { listTools: jest.fn().mockResolvedValue([]) } as any;\n createImpl.mockResolvedValue(fakeClient);\n\n const Inner: React.FC = () => {\n const servers = useTamboMcpServers();\n return (\n <div>\n <div data-testid=\"count\">{servers.length}</div>\n <div data-testid=\"urls\">{servers.map((s) => s.url).join(\",\")}</div>\n </div>\n );\n };\n\n const { getByTestId } = render(\n <TamboRegistryProvider>\n <TamboMcpProvider\n mcpServers={[{ url: \"https://one.example\" }, \"https://two.example\"]}\n >\n <Inner />\n </TamboMcpProvider>\n </TamboRegistryProvider>,\n );\n\n await waitFor(() => {\n expect(getByTestId(\"count\").textContent).toBe(\"2\");\n const urls = getByTestId(\"urls\").textContent || \"\";\n expect(urls).toContain(\"https://one.example\");\n expect(urls).toContain(\"https://two.example\");\n });\n });\n\n it(\"marks a successfully connected server with a client instance\", async () => {\n const fakeClient = { listTools: jest.fn().mockResolvedValue([]) } as any;\n createImpl.mockResolvedValue(fakeClient);\n\n let latest: McpServer[] = [];\n const Capture: React.FC = () => {\n const servers = useTamboMcpServers();\n useEffect(() => {\n latest = servers;\n }, [servers]);\n return null;\n };\n\n render(\n <TamboRegistryProvider>\n <TamboMcpProvider mcpServers={[{ url: \"https://ok.example\" }]}>\n <Capture />\n </TamboMcpProvider>\n </TamboRegistryProvider>,\n );\n\n await waitFor(() => {\n expect(latest.length).toBe(1);\n expect(\"client\" in latest[0]).toBe(true);\n expect((latest[0] as any).client).toBe(fakeClient);\n // no connectionError on connected server\n expect((latest[0] as any).connectionError).toBeUndefined();\n });\n });\n\n it(\"marks a failed server with a connectionError and no client\", async () => {\n const boom = new Error(\"boom\");\n createImpl.mockRejectedValue(boom);\n\n let latest: McpServer[] = [];\n const Capture: React.FC = () => {\n const servers = useTamboMcpServers();\n useEffect(() => {\n latest = servers;\n }, [servers]);\n return null;\n };\n\n render(\n <TamboRegistryProvider>\n <TamboMcpProvider mcpServers={[{ url: \"https://fail.example\" }]}>\n <Capture />\n </TamboMcpProvider>\n </TamboRegistryProvider>,\n );\n\n await waitFor(() => {\n expect(latest.length).toBe(1);\n expect(\"client\" in latest[0]).toBe(false);\n // @ts-expect-error narrowing at runtime\n expect(latest[0].connectionError).toBeInstanceOf(Error);\n // @ts-expect-error narrowing at runtime\n expect(latest[0].connectionError?.message).toBe(\"boom\");\n });\n });\n});\n"]}
@@ -1,3 +1,3 @@
1
1
  export { MCPTransport } from "./mcp-client";
2
- export { TamboMcpProvider, type McpServerInfo } from "./tambo-mcp-provider";
2
+ export { TamboMcpProvider, useTamboMcpServers, type ConnectedMcpServer, type FailedMcpServer, type McpServer, type McpServerInfo, } from "./tambo-mcp-provider";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,aAAa,GACnB,MAAM,sBAAsB,CAAC"}
package/dist/mcp/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TamboMcpProvider = exports.MCPTransport = void 0;
3
+ exports.useTamboMcpServers = exports.TamboMcpProvider = exports.MCPTransport = void 0;
4
4
  var mcp_client_1 = require("./mcp-client");
5
5
  Object.defineProperty(exports, "MCPTransport", { enumerable: true, get: function () { return mcp_client_1.MCPTransport; } });
6
6
  var tambo_mcp_provider_1 = require("./tambo-mcp-provider");
7
7
  Object.defineProperty(exports, "TamboMcpProvider", { enumerable: true, get: function () { return tambo_mcp_provider_1.TamboMcpProvider; } });
8
+ Object.defineProperty(exports, "useTamboMcpServers", { enumerable: true, get: function () { return tambo_mcp_provider_1.useTamboMcpServers; } });
8
9
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":";;;AAAA,2CAA4C;AAAnC,0GAAA,YAAY,OAAA;AACrB,2DAA4E;AAAnE,sHAAA,gBAAgB,OAAA","sourcesContent":["export { MCPTransport } from \"./mcp-client\";\nexport { TamboMcpProvider, type McpServerInfo } from \"./tambo-mcp-provider\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":";;;AAAA,2CAA4C;AAAnC,0GAAA,YAAY,OAAA;AACrB,2DAO8B;AAN5B,sHAAA,gBAAgB,OAAA;AAChB,wHAAA,kBAAkB,OAAA","sourcesContent":["export { MCPTransport } from \"./mcp-client\";\nexport {\n TamboMcpProvider,\n useTamboMcpServers,\n type ConnectedMcpServer,\n type FailedMcpServer,\n type McpServer,\n type McpServerInfo,\n} from \"./tambo-mcp-provider\";\n"]}
@@ -1,5 +1,5 @@
1
- import { FC } from "react";
2
- import { MCPTransport } from "./mcp-client";
1
+ import React, { FC } from "react";
2
+ import { MCPClient, 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.
@@ -14,6 +14,14 @@ export interface McpServerInfo {
14
14
  transport?: MCPTransport;
15
15
  customHeaders?: Record<string, string>;
16
16
  }
17
+ export interface ConnectedMcpServer extends McpServerInfo {
18
+ client: MCPClient;
19
+ }
20
+ export interface FailedMcpServer extends McpServerInfo {
21
+ client?: never;
22
+ connectionError: Error;
23
+ }
24
+ export type McpServer = ConnectedMcpServer | FailedMcpServer;
17
25
  /**
18
26
  * This provider is used to register tools from MCP servers.
19
27
  * @returns the wrapped children
@@ -22,4 +30,28 @@ export declare const TamboMcpProvider: FC<{
22
30
  mcpServers: (McpServerInfo | string)[];
23
31
  children: React.ReactNode;
24
32
  }>;
33
+ /**
34
+ * Hook to access the actual MCP servers, as they are connected (or fail to
35
+ * connect).
36
+ *
37
+ * You can call methods on the MCP client that is included in the MCP server
38
+ * object.
39
+ *
40
+ * If the server fails to connect, the `client` property will be `undefined` and
41
+ * the `connectionError` property will be set.
42
+ *
43
+ * For example, to forcibly disconnect and reconnect all MCP servers:
44
+ *
45
+ * ```tsx
46
+ * const mcpServers = useMcpServers();
47
+ * mcpServers.forEach((mcpServer) => {
48
+ * mcpServer.client?.reconnect();
49
+ * });
50
+ * ```
51
+ *
52
+ * Note that the MCP servers are not guaranteed to be in the same order as the
53
+ * input array, because they are added as they are connected.
54
+ * @returns The MCP servers
55
+ */
56
+ export declare const useTamboMcpServers: () => McpServer[];
25
57
  //# 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":"AAAA,OAAO,EAAE,EAAE,EAAa,MAAM,OAAO,CAAC;AAGtC,OAAO,EAAa,YAAY,EAAE,MAAM,cAAc,CAAC;AAEvD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAsB5D;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AACD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC;IAChC,UAAU,EAAE,CAAC,aAAa,GAAG,MAAM,CAAC,EAAE,CAAC;IACvC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAkEA,CAAC"}
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,EAIH,MAAM,OAAO,CAAC;AAGf,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEvD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAsB5D;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACvD,MAAM,EAAE,SAAS,CAAC;CACnB;AAED,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,eAAe,EAAE,KAAK,CAAC;CACxB;AAED,MAAM,MAAM,SAAS,GAAG,kBAAkB,GAAG,eAAe,CAAC;AAG7D;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC;IAChC,UAAU,EAAE,CAAC,aAAa,GAAG,MAAM,CAAC,EAAE,CAAC;IACvC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAsHA,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,kBAAkB,mBAE9B,CAAC"}
@@ -1,8 +1,41 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TamboMcpProvider = void 0;
36
+ exports.useTamboMcpServers = exports.TamboMcpProvider = void 0;
4
37
  exports.extractErrorMessage = extractErrorMessage;
5
- const react_1 = require("react");
38
+ const react_1 = __importStar(require("react"));
6
39
  const tambo_registry_provider_1 = require("../providers/tambo-registry-provider");
7
40
  const mcp_client_1 = require("./mcp-client");
8
41
  /**
@@ -28,28 +61,54 @@ function extractErrorMessage(content) {
28
61
  }
29
62
  return `${content}`;
30
63
  }
64
+ const McpProviderContext = (0, react_1.createContext)([]);
31
65
  /**
32
66
  * This provider is used to register tools from MCP servers.
33
67
  * @returns the wrapped children
34
68
  */
35
69
  const TamboMcpProvider = ({ mcpServers, children }) => {
36
70
  const { registerTool } = (0, tambo_registry_provider_1.useTamboRegistry)();
71
+ const [connectedMcpServers, setConnectedMcpServers] = (0, react_1.useState)([]);
37
72
  (0, react_1.useEffect)(() => {
38
73
  if (!mcpServers) {
39
74
  return;
40
75
  }
41
- async function registerMcpServers(mcpServers) {
76
+ async function registerMcpServers(mcpServerInfos) {
42
77
  // Maps tool names to the MCP client that registered them
43
78
  const mcpServerMap = new Map();
44
- const serverToolLists = mcpServers.map(async (mcpServer) => {
45
- const server = typeof mcpServer === "string"
46
- ? { url: mcpServer, transport: mcp_client_1.MCPTransport.SSE }
47
- : mcpServer;
48
- const { url, transport = mcp_client_1.MCPTransport.SSE, customHeaders } = server;
49
- const mcpClient = await mcp_client_1.MCPClient.create(url, transport, customHeaders);
50
- const tools = await mcpClient.listTools();
79
+ // initialize the MCP clients, converting McpServerInfo -> McpServer
80
+ const mcpServers = await Promise.allSettled(mcpServerInfos.map(async (mcpServerInfo) => {
81
+ try {
82
+ const client = await mcp_client_1.MCPClient.create(mcpServerInfo.url, mcpServerInfo.transport, mcpServerInfo.customHeaders);
83
+ const connectedMcpServer = {
84
+ ...mcpServerInfo,
85
+ client: client,
86
+ };
87
+ // note because the promises may resolve in any order, the resulting
88
+ // array may not be in the same order as the input array
89
+ setConnectedMcpServers((prev) => [...prev, connectedMcpServer]);
90
+ return connectedMcpServer;
91
+ }
92
+ catch (error) {
93
+ const failedMcpServer = {
94
+ ...mcpServerInfo,
95
+ connectionError: error,
96
+ };
97
+ // note because the promises may resolve in any order, the resulting
98
+ // array may not be in the same order as the input array
99
+ setConnectedMcpServers((prev) => [...prev, failedMcpServer]);
100
+ return failedMcpServer;
101
+ }
102
+ }));
103
+ // note do not rely on the state
104
+ const connectedMcpServers = mcpServers
105
+ .filter((result) => result.status === "fulfilled")
106
+ .map((result) => result.value);
107
+ // Now create a map of tool name to MCP client
108
+ const serverToolLists = connectedMcpServers.map(async (mcpServer) => {
109
+ const tools = (await mcpServer.client?.listTools()) ?? [];
51
110
  tools.forEach((tool) => {
52
- mcpServerMap.set(tool.name, mcpClient);
111
+ mcpServerMap.set(tool.name, mcpServer);
53
112
  });
54
113
  return tools;
55
114
  });
@@ -74,7 +133,11 @@ const TamboMcpProvider = ({ mcpServers, children }) => {
74
133
  // should never happen
75
134
  throw new Error(`MCP server for tool ${tool.name} not found`);
76
135
  }
77
- const result = await mcpServer.callTool(tool.name, args);
136
+ if (!mcpServer.client) {
137
+ // this can't actually happen because the tool can't be registered if the server is not connected
138
+ throw new Error(`MCP server for tool ${tool.name} is not connected`);
139
+ }
140
+ const result = await mcpServer.client.callTool(tool.name, args);
78
141
  if (result.isError) {
79
142
  const errorMessage = extractErrorMessage(result.content);
80
143
  throw new Error(errorMessage);
@@ -85,9 +148,40 @@ const TamboMcpProvider = ({ mcpServers, children }) => {
85
148
  });
86
149
  });
87
150
  }
88
- registerMcpServers(mcpServers);
151
+ // normalize the server infos
152
+ const mcpServerInfos = mcpServers.map((mcpServer) => typeof mcpServer === "string"
153
+ ? { url: mcpServer, transport: mcp_client_1.MCPTransport.SSE }
154
+ : mcpServer);
155
+ registerMcpServers(mcpServerInfos);
89
156
  }, [mcpServers, registerTool]);
90
- return children;
157
+ return (react_1.default.createElement(McpProviderContext.Provider, { value: connectedMcpServers }, children));
91
158
  };
92
159
  exports.TamboMcpProvider = TamboMcpProvider;
160
+ /**
161
+ * Hook to access the actual MCP servers, as they are connected (or fail to
162
+ * connect).
163
+ *
164
+ * You can call methods on the MCP client that is included in the MCP server
165
+ * object.
166
+ *
167
+ * If the server fails to connect, the `client` property will be `undefined` and
168
+ * the `connectionError` property will be set.
169
+ *
170
+ * For example, to forcibly disconnect and reconnect all MCP servers:
171
+ *
172
+ * ```tsx
173
+ * const mcpServers = useMcpServers();
174
+ * mcpServers.forEach((mcpServer) => {
175
+ * mcpServer.client?.reconnect();
176
+ * });
177
+ * ```
178
+ *
179
+ * Note that the MCP servers are not guaranteed to be in the same order as the
180
+ * input array, because they are added as they are connected.
181
+ * @returns The MCP servers
182
+ */
183
+ const useTamboMcpServers = () => {
184
+ return (0, react_1.useContext)(McpProviderContext);
185
+ };
186
+ exports.useTamboMcpServers = useTamboMcpServers;
93
187
  //# sourceMappingURL=tambo-mcp-provider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tambo-mcp-provider.js","sourceRoot":"","sources":["../../src/mcp/tambo-mcp-provider.tsx"],"names":[],"mappings":"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","sourcesContent":["import { FC, useEffect } from \"react\";\nimport { TamboTool } from \"../model/component-metadata\";\nimport { useTamboRegistry } from \"../providers/tambo-registry-provider\";\nimport { MCPClient, MCPTransport } from \"./mcp-client\";\n\n/**\n * Extracts error message from MCP tool result content.\n * Handles both array and string content formats.\n * Always returns a string, even for invalid/null inputs.\n * @returns The extracted error message as a string\n */\nexport function extractErrorMessage(content: unknown): string {\n if (content === undefined || content === null) {\n return \"Unknown error occurred\";\n }\n\n if (Array.isArray(content)) {\n const textItems = content\n .filter(\n (item) => item && item.type === \"text\" && typeof item.text === \"string\",\n )\n .map((item) => item.text);\n\n return textItems.length > 0\n ? textItems.join(\" \")\n : \"Error occurred but no details provided\";\n }\n\n if (typeof content === \"object\") {\n return JSON.stringify(content);\n }\n\n return `${content}`;\n}\n\nexport interface McpServerInfo {\n name?: string;\n url: string;\n description?: string;\n transport?: MCPTransport;\n customHeaders?: Record<string, string>;\n}\n/**\n * This provider is used to register tools from MCP servers.\n * @returns the wrapped children\n */\nexport const TamboMcpProvider: FC<{\n mcpServers: (McpServerInfo | string)[];\n children: React.ReactNode;\n}> = ({ mcpServers, children }) => {\n const { registerTool } = useTamboRegistry();\n\n useEffect(() => {\n if (!mcpServers) {\n return;\n }\n async function registerMcpServers(mcpServers: (McpServerInfo | string)[]) {\n // Maps tool names to the MCP client that registered them\n const mcpServerMap = new Map<string, MCPClient>();\n const serverToolLists = mcpServers.map(async (mcpServer) => {\n const server =\n typeof mcpServer === \"string\"\n ? { url: mcpServer, transport: MCPTransport.SSE }\n : mcpServer;\n const { url, transport = MCPTransport.SSE, customHeaders } = server;\n const mcpClient = await MCPClient.create(url, transport, customHeaders);\n const tools = await mcpClient.listTools();\n tools.forEach((tool) => {\n mcpServerMap.set(tool.name, mcpClient);\n });\n return tools;\n });\n const toolResults = await Promise.allSettled(serverToolLists);\n\n // Just log the failed tools, we can't do anything about them\n const failedTools = toolResults.filter(\n (result) => result.status === \"rejected\",\n );\n if (failedTools.length > 0) {\n console.error(\n \"Failed to register tools from MCP servers:\",\n failedTools.map((result) => result.reason),\n );\n }\n\n // Register the successful tools\n const allTools = toolResults\n .filter((result) => result.status === \"fulfilled\")\n .map((result) => result.value)\n .flat();\n allTools.forEach((tool) => {\n registerTool({\n description: tool.description ?? \"\",\n name: tool.name,\n tool: async (args: Record<string, unknown>) => {\n const mcpServer = mcpServerMap.get(tool.name);\n if (!mcpServer) {\n // should never happen\n throw new Error(`MCP server for tool ${tool.name} not found`);\n }\n const result = await mcpServer.callTool(tool.name, args);\n if (result.isError) {\n const errorMessage = extractErrorMessage(result.content);\n throw new Error(errorMessage);\n }\n return result.content;\n },\n toolSchema: tool.inputSchema as TamboTool[\"toolSchema\"],\n });\n });\n }\n registerMcpServers(mcpServers);\n }, [mcpServers, registerTool]);\n\n return children;\n};\n"]}
1
+ {"version":3,"file":"tambo-mcp-provider.js","sourceRoot":"","sources":["../../src/mcp/tambo-mcp-provider.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {\n createContext,\n FC,\n useContext,\n useEffect,\n useState,\n} from \"react\";\nimport { TamboTool } from \"../model/component-metadata\";\nimport { useTamboRegistry } from \"../providers/tambo-registry-provider\";\nimport { MCPClient, MCPTransport } from \"./mcp-client\";\n\n/**\n * Extracts error message from MCP tool result content.\n * Handles both array and string content formats.\n * Always returns a string, even for invalid/null inputs.\n * @returns The extracted error message as a string\n */\nexport function extractErrorMessage(content: unknown): string {\n if (content === undefined || content === null) {\n return \"Unknown error occurred\";\n }\n\n if (Array.isArray(content)) {\n const textItems = content\n .filter(\n (item) => item && item.type === \"text\" && typeof item.text === \"string\",\n )\n .map((item) => item.text);\n\n return textItems.length > 0\n ? textItems.join(\" \")\n : \"Error occurred but no details provided\";\n }\n\n if (typeof content === \"object\") {\n return JSON.stringify(content);\n }\n\n return `${content}`;\n}\n\nexport interface McpServerInfo {\n name?: string;\n url: string;\n description?: string;\n transport?: MCPTransport;\n customHeaders?: Record<string, string>;\n}\n\nexport interface ConnectedMcpServer extends McpServerInfo {\n client: MCPClient;\n}\n\nexport interface FailedMcpServer extends McpServerInfo {\n client?: never;\n connectionError: Error;\n}\n\nexport type McpServer = ConnectedMcpServer | FailedMcpServer;\n\nconst McpProviderContext = createContext<McpServer[]>([]);\n/**\n * This provider is used to register tools from MCP servers.\n * @returns the wrapped children\n */\nexport const TamboMcpProvider: FC<{\n mcpServers: (McpServerInfo | string)[];\n children: React.ReactNode;\n}> = ({ mcpServers, children }) => {\n const { registerTool } = useTamboRegistry();\n const [connectedMcpServers, setConnectedMcpServers] = useState<McpServer[]>(\n [],\n );\n\n useEffect(() => {\n if (!mcpServers) {\n return;\n }\n async function registerMcpServers(mcpServerInfos: McpServerInfo[]) {\n // Maps tool names to the MCP client that registered them\n const mcpServerMap = new Map<string, McpServer>();\n\n // initialize the MCP clients, converting McpServerInfo -> McpServer\n const mcpServers = await Promise.allSettled(\n mcpServerInfos.map(async (mcpServerInfo): Promise<McpServer> => {\n try {\n const client = await MCPClient.create(\n mcpServerInfo.url,\n mcpServerInfo.transport,\n mcpServerInfo.customHeaders,\n );\n const connectedMcpServer = {\n ...mcpServerInfo,\n client: client,\n };\n // note because the promises may resolve in any order, the resulting\n // array may not be in the same order as the input array\n setConnectedMcpServers((prev) => [...prev, connectedMcpServer]);\n return connectedMcpServer;\n } catch (error) {\n const failedMcpServer = {\n ...mcpServerInfo,\n connectionError: error as Error,\n };\n // note because the promises may resolve in any order, the resulting\n // array may not be in the same order as the input array\n setConnectedMcpServers((prev) => [...prev, failedMcpServer]);\n return failedMcpServer;\n }\n }),\n );\n\n // note do not rely on the state\n const connectedMcpServers = mcpServers\n .filter((result) => result.status === \"fulfilled\")\n .map((result) => result.value);\n\n // Now create a map of tool name to MCP client\n const serverToolLists = connectedMcpServers.map(async (mcpServer) => {\n const tools = (await mcpServer.client?.listTools()) ?? [];\n tools.forEach((tool) => {\n mcpServerMap.set(tool.name, mcpServer);\n });\n return tools;\n });\n const toolResults = await Promise.allSettled(serverToolLists);\n\n // Just log the failed tools, we can't do anything about them\n const failedTools = toolResults.filter(\n (result) => result.status === \"rejected\",\n );\n if (failedTools.length > 0) {\n console.error(\n \"Failed to register tools from MCP servers:\",\n failedTools.map((result) => result.reason),\n );\n }\n\n // Register the successful tools\n const allTools = toolResults\n .filter((result) => result.status === \"fulfilled\")\n .map((result) => result.value)\n .flat();\n allTools.forEach((tool) => {\n registerTool({\n description: tool.description ?? \"\",\n name: tool.name,\n tool: async (args: Record<string, unknown>) => {\n const mcpServer = mcpServerMap.get(tool.name);\n if (!mcpServer) {\n // should never happen\n throw new Error(`MCP server for tool ${tool.name} not found`);\n }\n if (!mcpServer.client) {\n // this can't actually happen because the tool can't be registered if the server is not connected\n throw new Error(\n `MCP server for tool ${tool.name} is not connected`,\n );\n }\n const result = await mcpServer.client.callTool(tool.name, args);\n if (result.isError) {\n const errorMessage = extractErrorMessage(result.content);\n throw new Error(errorMessage);\n }\n return result.content;\n },\n toolSchema: tool.inputSchema as TamboTool[\"toolSchema\"],\n });\n });\n }\n\n // normalize the server infos\n const mcpServerInfos = mcpServers.map((mcpServer) =>\n typeof mcpServer === \"string\"\n ? { url: mcpServer, transport: MCPTransport.SSE }\n : mcpServer,\n );\n\n registerMcpServers(mcpServerInfos);\n }, [mcpServers, registerTool]);\n\n return (\n <McpProviderContext.Provider value={connectedMcpServers}>\n {children}\n </McpProviderContext.Provider>\n );\n};\n\n/**\n * Hook to access the actual MCP servers, as they are connected (or fail to\n * connect).\n *\n * You can call methods on the MCP client that is included in the MCP server\n * object.\n *\n * If the server fails to connect, the `client` property will be `undefined` and\n * the `connectionError` property will be set.\n *\n * For example, to forcibly disconnect and reconnect all MCP servers:\n *\n * ```tsx\n * const mcpServers = useMcpServers();\n * mcpServers.forEach((mcpServer) => {\n * mcpServer.client?.reconnect();\n * });\n * ```\n *\n * Note that the MCP servers are not guaranteed to be in the same order as the\n * input array, because they are added as they are connected.\n * @returns The MCP servers\n */\nexport const useTamboMcpServers = () => {\n return useContext(McpProviderContext);\n};\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-mcp-servers.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-mcp-servers.test.d.ts","sourceRoot":"","sources":["../../../src/mcp/__tests__/use-mcp-servers.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,83 @@
1
+ import { render, waitFor } from "@testing-library/react";
2
+ import React, { useEffect } from "react";
3
+ import { TamboRegistryProvider } from "../../providers/tambo-registry-provider";
4
+ import { TamboMcpProvider, useTamboMcpServers, } from "../tambo-mcp-provider";
5
+ // Mock the registry to provide a no-op registerTool
6
+ // Do not mock the registry; use the real provider in render
7
+ // Mock the MCP client; use a mutable implementation to avoid TDZ issues
8
+ let createImpl = jest.fn();
9
+ jest.mock("../mcp-client", () => ({
10
+ MCPClient: { create: (...args) => createImpl(...args) },
11
+ MCPTransport: { SSE: "sse", HTTP: "http" },
12
+ }));
13
+ // Import after mocks note: jest.mock calls are hoisted, so standard imports are fine
14
+ describe("useTamboMcpServers + TamboMcpProvider", () => {
15
+ beforeEach(() => {
16
+ createImpl = jest.fn();
17
+ });
18
+ it("provides normalized MCP server entries to inner components", async () => {
19
+ const fakeClient = { listTools: jest.fn().mockResolvedValue([]) };
20
+ createImpl.mockResolvedValue(fakeClient);
21
+ const Inner = () => {
22
+ const servers = useTamboMcpServers();
23
+ return (React.createElement("div", null,
24
+ React.createElement("div", { "data-testid": "count" }, servers.length),
25
+ React.createElement("div", { "data-testid": "urls" }, servers.map((s) => s.url).join(","))));
26
+ };
27
+ const { getByTestId } = render(React.createElement(TamboRegistryProvider, null,
28
+ React.createElement(TamboMcpProvider, { mcpServers: [{ url: "https://one.example" }, "https://two.example"] },
29
+ React.createElement(Inner, null))));
30
+ await waitFor(() => {
31
+ expect(getByTestId("count").textContent).toBe("2");
32
+ const urls = getByTestId("urls").textContent || "";
33
+ expect(urls).toContain("https://one.example");
34
+ expect(urls).toContain("https://two.example");
35
+ });
36
+ });
37
+ it("marks a successfully connected server with a client instance", async () => {
38
+ const fakeClient = { listTools: jest.fn().mockResolvedValue([]) };
39
+ createImpl.mockResolvedValue(fakeClient);
40
+ let latest = [];
41
+ const Capture = () => {
42
+ const servers = useTamboMcpServers();
43
+ useEffect(() => {
44
+ latest = servers;
45
+ }, [servers]);
46
+ return null;
47
+ };
48
+ render(React.createElement(TamboRegistryProvider, null,
49
+ React.createElement(TamboMcpProvider, { mcpServers: [{ url: "https://ok.example" }] },
50
+ React.createElement(Capture, null))));
51
+ await waitFor(() => {
52
+ expect(latest.length).toBe(1);
53
+ expect("client" in latest[0]).toBe(true);
54
+ expect(latest[0].client).toBe(fakeClient);
55
+ // no connectionError on connected server
56
+ expect(latest[0].connectionError).toBeUndefined();
57
+ });
58
+ });
59
+ it("marks a failed server with a connectionError and no client", async () => {
60
+ const boom = new Error("boom");
61
+ createImpl.mockRejectedValue(boom);
62
+ let latest = [];
63
+ const Capture = () => {
64
+ const servers = useTamboMcpServers();
65
+ useEffect(() => {
66
+ latest = servers;
67
+ }, [servers]);
68
+ return null;
69
+ };
70
+ render(React.createElement(TamboRegistryProvider, null,
71
+ React.createElement(TamboMcpProvider, { mcpServers: [{ url: "https://fail.example" }] },
72
+ React.createElement(Capture, null))));
73
+ await waitFor(() => {
74
+ expect(latest.length).toBe(1);
75
+ expect("client" in latest[0]).toBe(false);
76
+ // @ts-expect-error narrowing at runtime
77
+ expect(latest[0].connectionError).toBeInstanceOf(Error);
78
+ // @ts-expect-error narrowing at runtime
79
+ expect(latest[0].connectionError?.message).toBe("boom");
80
+ });
81
+ });
82
+ });
83
+ //# sourceMappingURL=use-mcp-servers.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-mcp-servers.test.js","sourceRoot":"","sources":["../../../src/mcp/__tests__/use-mcp-servers.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import { render, waitFor } from \"@testing-library/react\";\nimport React, { useEffect } from \"react\";\nimport { TamboRegistryProvider } from \"../../providers/tambo-registry-provider\";\nimport {\n TamboMcpProvider,\n useTamboMcpServers,\n type McpServer,\n} from \"../tambo-mcp-provider\";\n\n// Mock the registry to provide a no-op registerTool\n// Do not mock the registry; use the real provider in render\n\n// Mock the MCP client; use a mutable implementation to avoid TDZ issues\nlet createImpl: jest.Mock<any, any> = jest.fn();\njest.mock(\"../mcp-client\", () => ({\n MCPClient: { create: (...args: any[]) => createImpl(...args) },\n MCPTransport: { SSE: \"sse\", HTTP: \"http\" },\n}));\n\n// Import after mocks note: jest.mock calls are hoisted, so standard imports are fine\n\ndescribe(\"useTamboMcpServers + TamboMcpProvider\", () => {\n beforeEach(() => {\n createImpl = jest.fn();\n });\n\n it(\"provides normalized MCP server entries to inner components\", async () => {\n const fakeClient = { listTools: jest.fn().mockResolvedValue([]) } as any;\n createImpl.mockResolvedValue(fakeClient);\n\n const Inner: React.FC = () => {\n const servers = useTamboMcpServers();\n return (\n <div>\n <div data-testid=\"count\">{servers.length}</div>\n <div data-testid=\"urls\">{servers.map((s) => s.url).join(\",\")}</div>\n </div>\n );\n };\n\n const { getByTestId } = render(\n <TamboRegistryProvider>\n <TamboMcpProvider\n mcpServers={[{ url: \"https://one.example\" }, \"https://two.example\"]}\n >\n <Inner />\n </TamboMcpProvider>\n </TamboRegistryProvider>,\n );\n\n await waitFor(() => {\n expect(getByTestId(\"count\").textContent).toBe(\"2\");\n const urls = getByTestId(\"urls\").textContent || \"\";\n expect(urls).toContain(\"https://one.example\");\n expect(urls).toContain(\"https://two.example\");\n });\n });\n\n it(\"marks a successfully connected server with a client instance\", async () => {\n const fakeClient = { listTools: jest.fn().mockResolvedValue([]) } as any;\n createImpl.mockResolvedValue(fakeClient);\n\n let latest: McpServer[] = [];\n const Capture: React.FC = () => {\n const servers = useTamboMcpServers();\n useEffect(() => {\n latest = servers;\n }, [servers]);\n return null;\n };\n\n render(\n <TamboRegistryProvider>\n <TamboMcpProvider mcpServers={[{ url: \"https://ok.example\" }]}>\n <Capture />\n </TamboMcpProvider>\n </TamboRegistryProvider>,\n );\n\n await waitFor(() => {\n expect(latest.length).toBe(1);\n expect(\"client\" in latest[0]).toBe(true);\n expect((latest[0] as any).client).toBe(fakeClient);\n // no connectionError on connected server\n expect((latest[0] as any).connectionError).toBeUndefined();\n });\n });\n\n it(\"marks a failed server with a connectionError and no client\", async () => {\n const boom = new Error(\"boom\");\n createImpl.mockRejectedValue(boom);\n\n let latest: McpServer[] = [];\n const Capture: React.FC = () => {\n const servers = useTamboMcpServers();\n useEffect(() => {\n latest = servers;\n }, [servers]);\n return null;\n };\n\n render(\n <TamboRegistryProvider>\n <TamboMcpProvider mcpServers={[{ url: \"https://fail.example\" }]}>\n <Capture />\n </TamboMcpProvider>\n </TamboRegistryProvider>,\n );\n\n await waitFor(() => {\n expect(latest.length).toBe(1);\n expect(\"client\" in latest[0]).toBe(false);\n // @ts-expect-error narrowing at runtime\n expect(latest[0].connectionError).toBeInstanceOf(Error);\n // @ts-expect-error narrowing at runtime\n expect(latest[0].connectionError?.message).toBe(\"boom\");\n });\n });\n});\n"]}
@@ -1,3 +1,3 @@
1
1
  export { MCPTransport } from "./mcp-client";
2
- export { TamboMcpProvider, type McpServerInfo } from "./tambo-mcp-provider";
2
+ export { TamboMcpProvider, useTamboMcpServers, type ConnectedMcpServer, type FailedMcpServer, type McpServer, type McpServerInfo, } from "./tambo-mcp-provider";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,aAAa,GACnB,MAAM,sBAAsB,CAAC"}
package/esm/mcp/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export { MCPTransport } from "./mcp-client";
2
- export { TamboMcpProvider } from "./tambo-mcp-provider";
2
+ export { TamboMcpProvider, useTamboMcpServers, } from "./tambo-mcp-provider";
3
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAsB,MAAM,sBAAsB,CAAC","sourcesContent":["export { MCPTransport } from \"./mcp-client\";\nexport { TamboMcpProvider, type McpServerInfo } from \"./tambo-mcp-provider\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACL,gBAAgB,EAChB,kBAAkB,GAKnB,MAAM,sBAAsB,CAAC","sourcesContent":["export { MCPTransport } from \"./mcp-client\";\nexport {\n TamboMcpProvider,\n useTamboMcpServers,\n type ConnectedMcpServer,\n type FailedMcpServer,\n type McpServer,\n type McpServerInfo,\n} from \"./tambo-mcp-provider\";\n"]}
@@ -1,5 +1,5 @@
1
- import { FC } from "react";
2
- import { MCPTransport } from "./mcp-client";
1
+ import React, { FC } from "react";
2
+ import { MCPClient, 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.
@@ -14,6 +14,14 @@ export interface McpServerInfo {
14
14
  transport?: MCPTransport;
15
15
  customHeaders?: Record<string, string>;
16
16
  }
17
+ export interface ConnectedMcpServer extends McpServerInfo {
18
+ client: MCPClient;
19
+ }
20
+ export interface FailedMcpServer extends McpServerInfo {
21
+ client?: never;
22
+ connectionError: Error;
23
+ }
24
+ export type McpServer = ConnectedMcpServer | FailedMcpServer;
17
25
  /**
18
26
  * This provider is used to register tools from MCP servers.
19
27
  * @returns the wrapped children
@@ -22,4 +30,28 @@ export declare const TamboMcpProvider: FC<{
22
30
  mcpServers: (McpServerInfo | string)[];
23
31
  children: React.ReactNode;
24
32
  }>;
33
+ /**
34
+ * Hook to access the actual MCP servers, as they are connected (or fail to
35
+ * connect).
36
+ *
37
+ * You can call methods on the MCP client that is included in the MCP server
38
+ * object.
39
+ *
40
+ * If the server fails to connect, the `client` property will be `undefined` and
41
+ * the `connectionError` property will be set.
42
+ *
43
+ * For example, to forcibly disconnect and reconnect all MCP servers:
44
+ *
45
+ * ```tsx
46
+ * const mcpServers = useMcpServers();
47
+ * mcpServers.forEach((mcpServer) => {
48
+ * mcpServer.client?.reconnect();
49
+ * });
50
+ * ```
51
+ *
52
+ * Note that the MCP servers are not guaranteed to be in the same order as the
53
+ * input array, because they are added as they are connected.
54
+ * @returns The MCP servers
55
+ */
56
+ export declare const useTamboMcpServers: () => McpServer[];
25
57
  //# 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":"AAAA,OAAO,EAAE,EAAE,EAAa,MAAM,OAAO,CAAC;AAGtC,OAAO,EAAa,YAAY,EAAE,MAAM,cAAc,CAAC;AAEvD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAsB5D;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AACD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC;IAChC,UAAU,EAAE,CAAC,aAAa,GAAG,MAAM,CAAC,EAAE,CAAC;IACvC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAkEA,CAAC"}
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,EAIH,MAAM,OAAO,CAAC;AAGf,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEvD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAsB5D;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACvD,MAAM,EAAE,SAAS,CAAC;CACnB;AAED,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,eAAe,EAAE,KAAK,CAAC;CACxB;AAED,MAAM,MAAM,SAAS,GAAG,kBAAkB,GAAG,eAAe,CAAC;AAG7D;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC;IAChC,UAAU,EAAE,CAAC,aAAa,GAAG,MAAM,CAAC,EAAE,CAAC;IACvC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAsHA,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,kBAAkB,mBAE9B,CAAC"}
@@ -1,4 +1,4 @@
1
- import { useEffect } from "react";
1
+ import React, { createContext, useContext, useEffect, useState, } from "react";
2
2
  import { useTamboRegistry } from "../providers/tambo-registry-provider";
3
3
  import { MCPClient, MCPTransport } from "./mcp-client";
4
4
  /**
@@ -24,28 +24,54 @@ export function extractErrorMessage(content) {
24
24
  }
25
25
  return `${content}`;
26
26
  }
27
+ const McpProviderContext = createContext([]);
27
28
  /**
28
29
  * This provider is used to register tools from MCP servers.
29
30
  * @returns the wrapped children
30
31
  */
31
32
  export const TamboMcpProvider = ({ mcpServers, children }) => {
32
33
  const { registerTool } = useTamboRegistry();
34
+ const [connectedMcpServers, setConnectedMcpServers] = useState([]);
33
35
  useEffect(() => {
34
36
  if (!mcpServers) {
35
37
  return;
36
38
  }
37
- async function registerMcpServers(mcpServers) {
39
+ async function registerMcpServers(mcpServerInfos) {
38
40
  // Maps tool names to the MCP client that registered them
39
41
  const mcpServerMap = new Map();
40
- const serverToolLists = mcpServers.map(async (mcpServer) => {
41
- const server = typeof mcpServer === "string"
42
- ? { url: mcpServer, transport: MCPTransport.SSE }
43
- : mcpServer;
44
- const { url, transport = MCPTransport.SSE, customHeaders } = server;
45
- const mcpClient = await MCPClient.create(url, transport, customHeaders);
46
- const tools = await mcpClient.listTools();
42
+ // initialize the MCP clients, converting McpServerInfo -> McpServer
43
+ const mcpServers = await Promise.allSettled(mcpServerInfos.map(async (mcpServerInfo) => {
44
+ try {
45
+ const client = await MCPClient.create(mcpServerInfo.url, mcpServerInfo.transport, mcpServerInfo.customHeaders);
46
+ const connectedMcpServer = {
47
+ ...mcpServerInfo,
48
+ client: client,
49
+ };
50
+ // note because the promises may resolve in any order, the resulting
51
+ // array may not be in the same order as the input array
52
+ setConnectedMcpServers((prev) => [...prev, connectedMcpServer]);
53
+ return connectedMcpServer;
54
+ }
55
+ catch (error) {
56
+ const failedMcpServer = {
57
+ ...mcpServerInfo,
58
+ connectionError: error,
59
+ };
60
+ // note because the promises may resolve in any order, the resulting
61
+ // array may not be in the same order as the input array
62
+ setConnectedMcpServers((prev) => [...prev, failedMcpServer]);
63
+ return failedMcpServer;
64
+ }
65
+ }));
66
+ // note do not rely on the state
67
+ const connectedMcpServers = mcpServers
68
+ .filter((result) => result.status === "fulfilled")
69
+ .map((result) => result.value);
70
+ // Now create a map of tool name to MCP client
71
+ const serverToolLists = connectedMcpServers.map(async (mcpServer) => {
72
+ const tools = (await mcpServer.client?.listTools()) ?? [];
47
73
  tools.forEach((tool) => {
48
- mcpServerMap.set(tool.name, mcpClient);
74
+ mcpServerMap.set(tool.name, mcpServer);
49
75
  });
50
76
  return tools;
51
77
  });
@@ -70,7 +96,11 @@ export const TamboMcpProvider = ({ mcpServers, children }) => {
70
96
  // should never happen
71
97
  throw new Error(`MCP server for tool ${tool.name} not found`);
72
98
  }
73
- const result = await mcpServer.callTool(tool.name, args);
99
+ if (!mcpServer.client) {
100
+ // this can't actually happen because the tool can't be registered if the server is not connected
101
+ throw new Error(`MCP server for tool ${tool.name} is not connected`);
102
+ }
103
+ const result = await mcpServer.client.callTool(tool.name, args);
74
104
  if (result.isError) {
75
105
  const errorMessage = extractErrorMessage(result.content);
76
106
  throw new Error(errorMessage);
@@ -81,8 +111,38 @@ export const TamboMcpProvider = ({ mcpServers, children }) => {
81
111
  });
82
112
  });
83
113
  }
84
- registerMcpServers(mcpServers);
114
+ // normalize the server infos
115
+ const mcpServerInfos = mcpServers.map((mcpServer) => typeof mcpServer === "string"
116
+ ? { url: mcpServer, transport: MCPTransport.SSE }
117
+ : mcpServer);
118
+ registerMcpServers(mcpServerInfos);
85
119
  }, [mcpServers, registerTool]);
86
- return children;
120
+ return (React.createElement(McpProviderContext.Provider, { value: connectedMcpServers }, children));
121
+ };
122
+ /**
123
+ * Hook to access the actual MCP servers, as they are connected (or fail to
124
+ * connect).
125
+ *
126
+ * You can call methods on the MCP client that is included in the MCP server
127
+ * object.
128
+ *
129
+ * If the server fails to connect, the `client` property will be `undefined` and
130
+ * the `connectionError` property will be set.
131
+ *
132
+ * For example, to forcibly disconnect and reconnect all MCP servers:
133
+ *
134
+ * ```tsx
135
+ * const mcpServers = useMcpServers();
136
+ * mcpServers.forEach((mcpServer) => {
137
+ * mcpServer.client?.reconnect();
138
+ * });
139
+ * ```
140
+ *
141
+ * Note that the MCP servers are not guaranteed to be in the same order as the
142
+ * input array, because they are added as they are connected.
143
+ * @returns The MCP servers
144
+ */
145
+ export const useTamboMcpServers = () => {
146
+ return useContext(McpProviderContext);
87
147
  };
88
148
  //# sourceMappingURL=tambo-mcp-provider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tambo-mcp-provider.js","sourceRoot":"","sources":["../../src/mcp/tambo-mcp-provider.tsx"],"names":[],"mappings":"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","sourcesContent":["import { FC, useEffect } from \"react\";\nimport { TamboTool } from \"../model/component-metadata\";\nimport { useTamboRegistry } from \"../providers/tambo-registry-provider\";\nimport { MCPClient, MCPTransport } from \"./mcp-client\";\n\n/**\n * Extracts error message from MCP tool result content.\n * Handles both array and string content formats.\n * Always returns a string, even for invalid/null inputs.\n * @returns The extracted error message as a string\n */\nexport function extractErrorMessage(content: unknown): string {\n if (content === undefined || content === null) {\n return \"Unknown error occurred\";\n }\n\n if (Array.isArray(content)) {\n const textItems = content\n .filter(\n (item) => item && item.type === \"text\" && typeof item.text === \"string\",\n )\n .map((item) => item.text);\n\n return textItems.length > 0\n ? textItems.join(\" \")\n : \"Error occurred but no details provided\";\n }\n\n if (typeof content === \"object\") {\n return JSON.stringify(content);\n }\n\n return `${content}`;\n}\n\nexport interface McpServerInfo {\n name?: string;\n url: string;\n description?: string;\n transport?: MCPTransport;\n customHeaders?: Record<string, string>;\n}\n/**\n * This provider is used to register tools from MCP servers.\n * @returns the wrapped children\n */\nexport const TamboMcpProvider: FC<{\n mcpServers: (McpServerInfo | string)[];\n children: React.ReactNode;\n}> = ({ mcpServers, children }) => {\n const { registerTool } = useTamboRegistry();\n\n useEffect(() => {\n if (!mcpServers) {\n return;\n }\n async function registerMcpServers(mcpServers: (McpServerInfo | string)[]) {\n // Maps tool names to the MCP client that registered them\n const mcpServerMap = new Map<string, MCPClient>();\n const serverToolLists = mcpServers.map(async (mcpServer) => {\n const server =\n typeof mcpServer === \"string\"\n ? { url: mcpServer, transport: MCPTransport.SSE }\n : mcpServer;\n const { url, transport = MCPTransport.SSE, customHeaders } = server;\n const mcpClient = await MCPClient.create(url, transport, customHeaders);\n const tools = await mcpClient.listTools();\n tools.forEach((tool) => {\n mcpServerMap.set(tool.name, mcpClient);\n });\n return tools;\n });\n const toolResults = await Promise.allSettled(serverToolLists);\n\n // Just log the failed tools, we can't do anything about them\n const failedTools = toolResults.filter(\n (result) => result.status === \"rejected\",\n );\n if (failedTools.length > 0) {\n console.error(\n \"Failed to register tools from MCP servers:\",\n failedTools.map((result) => result.reason),\n );\n }\n\n // Register the successful tools\n const allTools = toolResults\n .filter((result) => result.status === \"fulfilled\")\n .map((result) => result.value)\n .flat();\n allTools.forEach((tool) => {\n registerTool({\n description: tool.description ?? \"\",\n name: tool.name,\n tool: async (args: Record<string, unknown>) => {\n const mcpServer = mcpServerMap.get(tool.name);\n if (!mcpServer) {\n // should never happen\n throw new Error(`MCP server for tool ${tool.name} not found`);\n }\n const result = await mcpServer.callTool(tool.name, args);\n if (result.isError) {\n const errorMessage = extractErrorMessage(result.content);\n throw new Error(errorMessage);\n }\n return result.content;\n },\n toolSchema: tool.inputSchema as TamboTool[\"toolSchema\"],\n });\n });\n }\n registerMcpServers(mcpServers);\n }, [mcpServers, registerTool]);\n\n return children;\n};\n"]}
1
+ {"version":3,"file":"tambo-mcp-provider.js","sourceRoot":"","sources":["../../src/mcp/tambo-mcp-provider.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {\n createContext,\n FC,\n useContext,\n useEffect,\n useState,\n} from \"react\";\nimport { TamboTool } from \"../model/component-metadata\";\nimport { useTamboRegistry } from \"../providers/tambo-registry-provider\";\nimport { MCPClient, MCPTransport } from \"./mcp-client\";\n\n/**\n * Extracts error message from MCP tool result content.\n * Handles both array and string content formats.\n * Always returns a string, even for invalid/null inputs.\n * @returns The extracted error message as a string\n */\nexport function extractErrorMessage(content: unknown): string {\n if (content === undefined || content === null) {\n return \"Unknown error occurred\";\n }\n\n if (Array.isArray(content)) {\n const textItems = content\n .filter(\n (item) => item && item.type === \"text\" && typeof item.text === \"string\",\n )\n .map((item) => item.text);\n\n return textItems.length > 0\n ? textItems.join(\" \")\n : \"Error occurred but no details provided\";\n }\n\n if (typeof content === \"object\") {\n return JSON.stringify(content);\n }\n\n return `${content}`;\n}\n\nexport interface McpServerInfo {\n name?: string;\n url: string;\n description?: string;\n transport?: MCPTransport;\n customHeaders?: Record<string, string>;\n}\n\nexport interface ConnectedMcpServer extends McpServerInfo {\n client: MCPClient;\n}\n\nexport interface FailedMcpServer extends McpServerInfo {\n client?: never;\n connectionError: Error;\n}\n\nexport type McpServer = ConnectedMcpServer | FailedMcpServer;\n\nconst McpProviderContext = createContext<McpServer[]>([]);\n/**\n * This provider is used to register tools from MCP servers.\n * @returns the wrapped children\n */\nexport const TamboMcpProvider: FC<{\n mcpServers: (McpServerInfo | string)[];\n children: React.ReactNode;\n}> = ({ mcpServers, children }) => {\n const { registerTool } = useTamboRegistry();\n const [connectedMcpServers, setConnectedMcpServers] = useState<McpServer[]>(\n [],\n );\n\n useEffect(() => {\n if (!mcpServers) {\n return;\n }\n async function registerMcpServers(mcpServerInfos: McpServerInfo[]) {\n // Maps tool names to the MCP client that registered them\n const mcpServerMap = new Map<string, McpServer>();\n\n // initialize the MCP clients, converting McpServerInfo -> McpServer\n const mcpServers = await Promise.allSettled(\n mcpServerInfos.map(async (mcpServerInfo): Promise<McpServer> => {\n try {\n const client = await MCPClient.create(\n mcpServerInfo.url,\n mcpServerInfo.transport,\n mcpServerInfo.customHeaders,\n );\n const connectedMcpServer = {\n ...mcpServerInfo,\n client: client,\n };\n // note because the promises may resolve in any order, the resulting\n // array may not be in the same order as the input array\n setConnectedMcpServers((prev) => [...prev, connectedMcpServer]);\n return connectedMcpServer;\n } catch (error) {\n const failedMcpServer = {\n ...mcpServerInfo,\n connectionError: error as Error,\n };\n // note because the promises may resolve in any order, the resulting\n // array may not be in the same order as the input array\n setConnectedMcpServers((prev) => [...prev, failedMcpServer]);\n return failedMcpServer;\n }\n }),\n );\n\n // note do not rely on the state\n const connectedMcpServers = mcpServers\n .filter((result) => result.status === \"fulfilled\")\n .map((result) => result.value);\n\n // Now create a map of tool name to MCP client\n const serverToolLists = connectedMcpServers.map(async (mcpServer) => {\n const tools = (await mcpServer.client?.listTools()) ?? [];\n tools.forEach((tool) => {\n mcpServerMap.set(tool.name, mcpServer);\n });\n return tools;\n });\n const toolResults = await Promise.allSettled(serverToolLists);\n\n // Just log the failed tools, we can't do anything about them\n const failedTools = toolResults.filter(\n (result) => result.status === \"rejected\",\n );\n if (failedTools.length > 0) {\n console.error(\n \"Failed to register tools from MCP servers:\",\n failedTools.map((result) => result.reason),\n );\n }\n\n // Register the successful tools\n const allTools = toolResults\n .filter((result) => result.status === \"fulfilled\")\n .map((result) => result.value)\n .flat();\n allTools.forEach((tool) => {\n registerTool({\n description: tool.description ?? \"\",\n name: tool.name,\n tool: async (args: Record<string, unknown>) => {\n const mcpServer = mcpServerMap.get(tool.name);\n if (!mcpServer) {\n // should never happen\n throw new Error(`MCP server for tool ${tool.name} not found`);\n }\n if (!mcpServer.client) {\n // this can't actually happen because the tool can't be registered if the server is not connected\n throw new Error(\n `MCP server for tool ${tool.name} is not connected`,\n );\n }\n const result = await mcpServer.client.callTool(tool.name, args);\n if (result.isError) {\n const errorMessage = extractErrorMessage(result.content);\n throw new Error(errorMessage);\n }\n return result.content;\n },\n toolSchema: tool.inputSchema as TamboTool[\"toolSchema\"],\n });\n });\n }\n\n // normalize the server infos\n const mcpServerInfos = mcpServers.map((mcpServer) =>\n typeof mcpServer === \"string\"\n ? { url: mcpServer, transport: MCPTransport.SSE }\n : mcpServer,\n );\n\n registerMcpServers(mcpServerInfos);\n }, [mcpServers, registerTool]);\n\n return (\n <McpProviderContext.Provider value={connectedMcpServers}>\n {children}\n </McpProviderContext.Provider>\n );\n};\n\n/**\n * Hook to access the actual MCP servers, as they are connected (or fail to\n * connect).\n *\n * You can call methods on the MCP client that is included in the MCP server\n * object.\n *\n * If the server fails to connect, the `client` property will be `undefined` and\n * the `connectionError` property will be set.\n *\n * For example, to forcibly disconnect and reconnect all MCP servers:\n *\n * ```tsx\n * const mcpServers = useMcpServers();\n * mcpServers.forEach((mcpServer) => {\n * mcpServer.client?.reconnect();\n * });\n * ```\n *\n * Note that the MCP servers are not guaranteed to be in the same order as the\n * input array, because they are added as they are connected.\n * @returns The MCP servers\n */\nexport const useTamboMcpServers = () => {\n return useContext(McpProviderContext);\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tambo-ai/react",
3
- "version": "0.53.2",
3
+ "version": "0.54.0",
4
4
  "description": "React client package for Tambo AI",
5
5
  "repository": {
6
6
  "type": "git",