@assistant-ui/react-mcp 0.0.22 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/auth/createOAuthProvider.d.ts +1 -1
  2. package/dist/auth/createOAuthProvider.js.map +1 -1
  3. package/dist/auth/types.d.ts +1 -1
  4. package/dist/index.d.ts +3 -2
  5. package/dist/index.js +2 -1
  6. package/dist/mcp-scope.d.ts +32 -1
  7. package/dist/mcp-scope.d.ts.map +1 -1
  8. package/dist/primitives/elicitation/McpElicitationPrimitiveAccept.d.ts +13 -0
  9. package/dist/primitives/elicitation/McpElicitationPrimitiveAccept.d.ts.map +1 -0
  10. package/dist/primitives/elicitation/McpElicitationPrimitiveAccept.js +106 -0
  11. package/dist/primitives/elicitation/McpElicitationPrimitiveAccept.js.map +1 -0
  12. package/dist/primitives/elicitation/McpElicitationPrimitiveCancel.d.ts +13 -0
  13. package/dist/primitives/elicitation/McpElicitationPrimitiveCancel.d.ts.map +1 -0
  14. package/dist/primitives/elicitation/McpElicitationPrimitiveCancel.js +43 -0
  15. package/dist/primitives/elicitation/McpElicitationPrimitiveCancel.js.map +1 -0
  16. package/dist/primitives/elicitation/McpElicitationPrimitiveDecline.d.ts +13 -0
  17. package/dist/primitives/elicitation/McpElicitationPrimitiveDecline.d.ts.map +1 -0
  18. package/dist/primitives/elicitation/McpElicitationPrimitiveDecline.js +43 -0
  19. package/dist/primitives/elicitation/McpElicitationPrimitiveDecline.js.map +1 -0
  20. package/dist/primitives/elicitation/McpElicitationPrimitiveError.d.ts +13 -0
  21. package/dist/primitives/elicitation/McpElicitationPrimitiveError.d.ts.map +1 -0
  22. package/dist/primitives/elicitation/McpElicitationPrimitiveError.js +38 -0
  23. package/dist/primitives/elicitation/McpElicitationPrimitiveError.js.map +1 -0
  24. package/dist/primitives/elicitation/McpElicitationPrimitiveFields.d.ts +12 -0
  25. package/dist/primitives/elicitation/McpElicitationPrimitiveFields.d.ts.map +1 -0
  26. package/dist/primitives/elicitation/McpElicitationPrimitiveFields.js +53 -0
  27. package/dist/primitives/elicitation/McpElicitationPrimitiveFields.js.map +1 -0
  28. package/dist/primitives/elicitation/McpElicitationPrimitiveItems.d.ts +13 -0
  29. package/dist/primitives/elicitation/McpElicitationPrimitiveItems.d.ts.map +1 -0
  30. package/dist/primitives/elicitation/McpElicitationPrimitiveItems.js +96 -0
  31. package/dist/primitives/elicitation/McpElicitationPrimitiveItems.js.map +1 -0
  32. package/dist/primitives/elicitation/McpElicitationPrimitiveMessage.d.ts +13 -0
  33. package/dist/primitives/elicitation/McpElicitationPrimitiveMessage.d.ts.map +1 -0
  34. package/dist/primitives/elicitation/McpElicitationPrimitiveMessage.js +29 -0
  35. package/dist/primitives/elicitation/McpElicitationPrimitiveMessage.js.map +1 -0
  36. package/dist/primitives/elicitation/McpElicitationPrimitiveRoot.d.ts +13 -0
  37. package/dist/primitives/elicitation/McpElicitationPrimitiveRoot.d.ts.map +1 -0
  38. package/dist/primitives/elicitation/McpElicitationPrimitiveRoot.js +28 -0
  39. package/dist/primitives/elicitation/McpElicitationPrimitiveRoot.js.map +1 -0
  40. package/dist/primitives/elicitation/context.d.ts +21 -0
  41. package/dist/primitives/elicitation/context.d.ts.map +1 -0
  42. package/dist/primitives/elicitation/context.js +21 -0
  43. package/dist/primitives/elicitation/context.js.map +1 -0
  44. package/dist/primitives/elicitation/initialElicitationDraft.d.ts +5 -0
  45. package/dist/primitives/elicitation/initialElicitationDraft.d.ts.map +1 -0
  46. package/dist/primitives/elicitation/initialElicitationDraft.js +27 -0
  47. package/dist/primitives/elicitation/initialElicitationDraft.js.map +1 -0
  48. package/dist/primitives/elicitation/prepareElicitationContent.d.ts +9 -0
  49. package/dist/primitives/elicitation/prepareElicitationContent.d.ts.map +1 -0
  50. package/dist/primitives/elicitation/prepareElicitationContent.js +46 -0
  51. package/dist/primitives/elicitation/prepareElicitationContent.js.map +1 -0
  52. package/dist/primitives/elicitation.d.ts +15 -0
  53. package/dist/primitives/elicitation.d.ts.map +1 -0
  54. package/dist/primitives/elicitation.js +27 -0
  55. package/dist/primitives/elicitation.js.map +1 -0
  56. package/dist/resources/McpManagerResource.d.ts.map +1 -1
  57. package/dist/resources/McpManagerResource.js +17 -3
  58. package/dist/resources/McpManagerResource.js.map +1 -1
  59. package/dist/resources/McpServerResource.d.ts +4 -0
  60. package/dist/resources/McpServerResource.d.ts.map +1 -1
  61. package/dist/resources/McpServerResource.js +153 -42
  62. package/dist/resources/McpServerResource.js.map +1 -1
  63. package/dist/resources/storage/McpLocalStorage.js +1 -1
  64. package/dist/resources/storage/McpLocalStorage.js.map +1 -1
  65. package/dist/resources/validateElicitationContent.d.ts +9 -0
  66. package/dist/resources/validateElicitationContent.d.ts.map +1 -0
  67. package/dist/resources/validateElicitationContent.js +47 -0
  68. package/dist/resources/validateElicitationContent.js.map +1 -0
  69. package/package.json +7 -6
  70. package/src/auth/createOAuthProvider.ts +2 -2
  71. package/src/auth/types.ts +1 -1
  72. package/src/index.ts +4 -0
  73. package/src/mcp-scope.ts +32 -0
  74. package/src/primitives/elicitation/McpElicitationPrimitiveAccept.tsx +48 -0
  75. package/src/primitives/elicitation/McpElicitationPrimitiveCancel.tsx +37 -0
  76. package/src/primitives/elicitation/McpElicitationPrimitiveDecline.tsx +37 -0
  77. package/src/primitives/elicitation/McpElicitationPrimitiveError.tsx +31 -0
  78. package/src/primitives/elicitation/McpElicitationPrimitiveFields.tsx +56 -0
  79. package/src/primitives/elicitation/McpElicitationPrimitiveItems.tsx +58 -0
  80. package/src/primitives/elicitation/McpElicitationPrimitiveMessage.tsx +26 -0
  81. package/src/primitives/elicitation/McpElicitationPrimitiveRoot.tsx +24 -0
  82. package/src/primitives/elicitation/context.tsx +45 -0
  83. package/src/primitives/elicitation/initialElicitationDraft.test.ts +34 -0
  84. package/src/primitives/elicitation/initialElicitationDraft.ts +39 -0
  85. package/src/primitives/elicitation/prepareElicitationContent.test.ts +415 -0
  86. package/src/primitives/elicitation/prepareElicitationContent.ts +116 -0
  87. package/src/primitives/elicitation.ts +14 -0
  88. package/src/resources/McpManagerResource.test.ts +173 -3
  89. package/src/resources/McpManagerResource.ts +37 -3
  90. package/src/resources/McpServerResource.test.ts +735 -4
  91. package/src/resources/McpServerResource.ts +237 -29
  92. package/src/resources/storage/McpLocalStorage.ts +1 -1
  93. package/src/resources/validateElicitationContent.test.ts +123 -0
  94. package/src/resources/validateElicitationContent.ts +79 -0
@@ -1,10 +1,39 @@
1
1
  import { createTapRoot, useResource } from "@assistant-ui/tap";
2
- import { describe, expect, it } from "vitest";
2
+ import { describe, expect, it, vi } from "vitest";
3
3
  import { defineConnector } from "../connector";
4
4
  import type { MCPConnector } from "../mcp-scope";
5
5
  import { assertUniqueServerIds } from "../utils/serverId";
6
6
  import { McpManagerResource } from "./McpManagerResource";
7
+ import { McpCustomStorage } from "./storage/McpCustomStorage";
7
8
  import { McpMemoryStorage } from "./storage/McpMemoryStorage";
9
+ import type { MCPStorageElement } from "./storage/types";
10
+
11
+ const mocks = vi.hoisted(() => {
12
+ const Client = vi.fn().mockImplementation(function Client(this: any) {
13
+ this.connect = vi.fn(async () => {});
14
+ this.listTools = vi.fn(async () => ({ tools: [] }));
15
+ this.setRequestHandler = vi.fn();
16
+ this.setNotificationHandler = vi.fn();
17
+ });
18
+ const StreamableHTTPClientTransport = vi
19
+ .fn()
20
+ .mockImplementation(function StreamableHTTPClientTransport(this: any) {
21
+ this.close = vi.fn(async () => {});
22
+ });
23
+
24
+ return { Client, StreamableHTTPClientTransport };
25
+ });
26
+
27
+ vi.mock("@modelcontextprotocol/client", async (importOriginal) => ({
28
+ ...(await importOriginal()),
29
+ Client: mocks.Client,
30
+ StreamableHTTPClientTransport: mocks.StreamableHTTPClientTransport,
31
+ }));
32
+
33
+ vi.mock("@assistant-ui/store", async (importOriginal) => ({
34
+ ...(await importOriginal()),
35
+ useAssistantClientRef: () => ({ current: null }),
36
+ }));
8
37
 
9
38
  const connector = (id: string, name = id): MCPConnector =>
10
39
  defineConnector({
@@ -14,12 +43,15 @@ const connector = (id: string, name = id): MCPConnector =>
14
43
  auth: { type: "none" },
15
44
  });
16
45
 
17
- const mount = (connectors: MCPConnector[]) =>
46
+ const mount = (
47
+ connectors: MCPConnector[],
48
+ storage: MCPStorageElement = McpMemoryStorage(),
49
+ ) =>
18
50
  createTapRoot(function Root() {
19
51
  return useResource(
20
52
  McpManagerResource({
21
53
  connectors,
22
- storage: McpMemoryStorage(),
54
+ storage,
23
55
  autoConnect: false,
24
56
  }),
25
57
  );
@@ -37,4 +69,142 @@ describe("McpManagerResource server ids", () => {
37
69
  it("allows distinct ids", () => {
38
70
  expect(() => assertUniqueServerIds(["docs", "linear"])).not.toThrow();
39
71
  });
72
+
73
+ it("passes connector cache configuration to its client", async () => {
74
+ mocks.Client.mockClear();
75
+ const root = mount([
76
+ defineConnector({
77
+ id: "docs",
78
+ name: "Docs",
79
+ url: "https://example.com/docs/mcp",
80
+ auth: { type: "none" },
81
+ cache: { defaultTtlMs: 5_000 },
82
+ }),
83
+ ]);
84
+
85
+ try {
86
+ await root.getValue().connector({ index: 0 }).connect();
87
+
88
+ expect(mocks.Client).toHaveBeenCalledWith(
89
+ {
90
+ name: "assistant-ui-mcp",
91
+ version: "0.0.0",
92
+ },
93
+ expect.objectContaining({ defaultCacheTtlMs: 5_000 }),
94
+ );
95
+ } finally {
96
+ root.unmount();
97
+ }
98
+ });
99
+
100
+ it("passes custom server cache configuration to its client", async () => {
101
+ mocks.Client.mockClear();
102
+ const root = mount([]);
103
+
104
+ try {
105
+ const id = await root.getValue().addCustomServer({
106
+ name: "Docs",
107
+ url: "https://example.com/docs/mcp",
108
+ auth: { type: "none" },
109
+ cache: { defaultTtlMs: 5_000 },
110
+ });
111
+
112
+ await vi.waitFor(() =>
113
+ expect(root.getValue().getState().customServers).toHaveLength(1),
114
+ );
115
+ await root.getValue().server({ id }).connect();
116
+
117
+ expect(mocks.Client).toHaveBeenCalledWith(
118
+ {
119
+ name: "assistant-ui-mcp",
120
+ version: "0.0.0",
121
+ },
122
+ expect.objectContaining({ defaultCacheTtlMs: 5_000 }),
123
+ );
124
+ } finally {
125
+ root.unmount();
126
+ }
127
+ });
128
+ });
129
+
130
+ describe("McpManagerResource storage failures", () => {
131
+ it("handles custom server load failures", async () => {
132
+ const error = new Error("load failed");
133
+ const consoleError = vi
134
+ .spyOn(console, "error")
135
+ .mockImplementation(() => {});
136
+ const root = mount(
137
+ [],
138
+ McpCustomStorage({
139
+ loadCustomServers: vi.fn(async () => {
140
+ throw error;
141
+ }),
142
+ saveCustomServers: vi.fn(async () => {}),
143
+ loadAuthState: vi.fn(async () => null),
144
+ saveAuthState: vi.fn(async () => {}),
145
+ clearAuthState: vi.fn(async () => {}),
146
+ }),
147
+ );
148
+
149
+ try {
150
+ await vi.waitFor(() =>
151
+ expect(root.getValue().getState().isHydrated).toBe(true),
152
+ );
153
+ expect(root.getValue().getState().customServers).toHaveLength(0);
154
+ expect(consoleError).toHaveBeenCalledWith(
155
+ "[assistant-ui/react-mcp] failed to load custom servers:",
156
+ error,
157
+ );
158
+ } finally {
159
+ root.unmount();
160
+ consoleError.mockRestore();
161
+ }
162
+ });
163
+
164
+ it("handles custom server save failures", async () => {
165
+ const error = new Error("save failed");
166
+ const consoleError = vi
167
+ .spyOn(console, "error")
168
+ .mockImplementation(() => {});
169
+ const saveCustomServers = vi.fn(async () => {});
170
+ const root = mount(
171
+ [],
172
+ McpCustomStorage({
173
+ loadCustomServers: vi.fn(async () => []),
174
+ saveCustomServers,
175
+ loadAuthState: vi.fn(async () => null),
176
+ saveAuthState: vi.fn(async () => {}),
177
+ clearAuthState: vi.fn(async () => {}),
178
+ }),
179
+ );
180
+
181
+ try {
182
+ await vi.waitFor(() =>
183
+ expect(root.getValue().getState().isHydrated).toBe(true),
184
+ );
185
+ await vi.waitFor(() => expect(saveCustomServers).toHaveBeenCalled());
186
+ saveCustomServers.mockClear();
187
+ saveCustomServers.mockRejectedValue(error);
188
+
189
+ await root.getValue().addCustomServer({
190
+ name: "Docs",
191
+ url: "https://example.com/docs/mcp",
192
+ auth: { type: "none" },
193
+ });
194
+
195
+ await vi.waitFor(() => {
196
+ expect(saveCustomServers).toHaveBeenCalledWith([
197
+ expect.objectContaining({ name: "Docs" }),
198
+ ]);
199
+ expect(root.getValue().getState().customServers).toHaveLength(1);
200
+ });
201
+ expect(consoleError).toHaveBeenCalledWith(
202
+ "[assistant-ui/react-mcp] failed to save custom servers:",
203
+ error,
204
+ );
205
+ } finally {
206
+ root.unmount();
207
+ consoleError.mockRestore();
208
+ }
209
+ });
40
210
  });
@@ -39,6 +39,16 @@ function defaultRedirectUri(): string {
39
39
  // array each render (which would invalidate the serverElements memo below).
40
40
  const NO_CONNECTORS: MCPConnector[] = [];
41
41
 
42
+ const reportCustomStorageFailure = (
43
+ operation: "load" | "save",
44
+ error: unknown,
45
+ ) => {
46
+ console.error(
47
+ `[assistant-ui/react-mcp] failed to ${operation} custom servers:`,
48
+ error,
49
+ );
50
+ };
51
+
42
52
  const useMcpManagerResource = (
43
53
  props: McpManagerResourceProps,
44
54
  ): ClientOutput<"mcp"> => {
@@ -69,8 +79,11 @@ const useMcpManagerResource = (
69
79
  try {
70
80
  records = await storage.loadCustomServers();
71
81
  } catch (error) {
82
+ if (!signal.cancelled) {
83
+ reportCustomStorageFailure("load", error);
84
+ }
72
85
  markHydrated();
73
- throw error;
86
+ return;
74
87
  }
75
88
 
76
89
  if (signal.cancelled) return;
@@ -96,7 +109,11 @@ const useMcpManagerResource = (
96
109
  const persistCustomServers = useEffectEvent(
97
110
  async (records: MCPCustomServerRecord[]) => {
98
111
  if (!hydratedRef.current) return;
99
- await storage.saveCustomServers(records);
112
+ try {
113
+ await storage.saveCustomServers(records);
114
+ } catch (error) {
115
+ reportCustomStorageFailure("save", error);
116
+ }
100
117
  },
101
118
  );
102
119
 
@@ -124,6 +141,10 @@ const useMcpManagerResource = (
124
141
  redirectUri,
125
142
  autoConnect,
126
143
  connectionTimeout: c.connectionTimeout ?? connectionTimeout,
144
+ ...(c.cache !== undefined ? { cache: c.cache } : {}),
145
+ ...(c.elicitation !== undefined
146
+ ? { elicitation: c.elicitation }
147
+ : {}),
127
148
  onRemove: async () => {
128
149
  // connectors cannot be removed
129
150
  },
@@ -143,6 +164,10 @@ const useMcpManagerResource = (
143
164
  redirectUri,
144
165
  autoConnect,
145
166
  connectionTimeout: s.connectionTimeout ?? connectionTimeout,
167
+ ...(s.cache !== undefined ? { cache: s.cache } : {}),
168
+ ...(s.elicitation !== undefined
169
+ ? { elicitation: s.elicitation }
170
+ : {}),
146
171
  onRemove: async () => {
147
172
  setCustomServers((prev) => prev.filter((x) => x.id !== s.id));
148
173
  },
@@ -226,7 +251,14 @@ const useMcpManagerResource = (
226
251
  },
227
252
  connector: ({ index }) => serverByKind("connector", index),
228
253
  customServer: ({ index }) => serverByKind("custom", index),
229
- addCustomServer: async ({ name, url, auth, connectionTimeout }) => {
254
+ addCustomServer: async ({
255
+ name,
256
+ url,
257
+ auth,
258
+ connectionTimeout,
259
+ cache,
260
+ elicitation,
261
+ }) => {
230
262
  const record: MCPCustomServerRecord = {
231
263
  id:
232
264
  typeof crypto !== "undefined" && "randomUUID" in crypto
@@ -236,6 +268,8 @@ const useMcpManagerResource = (
236
268
  url,
237
269
  auth: auth as MCPAuthConfig,
238
270
  connectionTimeout,
271
+ ...(cache !== undefined ? { cache } : {}),
272
+ ...(elicitation !== undefined ? { elicitation } : {}),
239
273
  createdAt: Date.now(),
240
274
  };
241
275
  setCustomServers((prev) => [...prev, record]);