@assistant-ui/react-mcp 0.0.22 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/createOAuthProvider.d.ts +1 -1
- package/dist/auth/createOAuthProvider.js.map +1 -1
- package/dist/auth/types.d.ts +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/mcp-scope.d.ts +32 -1
- package/dist/mcp-scope.d.ts.map +1 -1
- package/dist/primitives/elicitation/McpElicitationPrimitiveAccept.d.ts +13 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveAccept.d.ts.map +1 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveAccept.js +106 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveAccept.js.map +1 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveCancel.d.ts +13 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveCancel.d.ts.map +1 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveCancel.js +43 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveCancel.js.map +1 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveDecline.d.ts +13 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveDecline.d.ts.map +1 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveDecline.js +43 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveDecline.js.map +1 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveError.d.ts +13 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveError.d.ts.map +1 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveError.js +38 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveError.js.map +1 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveFields.d.ts +12 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveFields.d.ts.map +1 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveFields.js +53 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveFields.js.map +1 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveItems.d.ts +13 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveItems.d.ts.map +1 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveItems.js +96 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveItems.js.map +1 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveMessage.d.ts +13 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveMessage.d.ts.map +1 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveMessage.js +29 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveMessage.js.map +1 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveRoot.d.ts +13 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveRoot.d.ts.map +1 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveRoot.js +28 -0
- package/dist/primitives/elicitation/McpElicitationPrimitiveRoot.js.map +1 -0
- package/dist/primitives/elicitation/context.d.ts +21 -0
- package/dist/primitives/elicitation/context.d.ts.map +1 -0
- package/dist/primitives/elicitation/context.js +21 -0
- package/dist/primitives/elicitation/context.js.map +1 -0
- package/dist/primitives/elicitation/initialElicitationDraft.d.ts +5 -0
- package/dist/primitives/elicitation/initialElicitationDraft.d.ts.map +1 -0
- package/dist/primitives/elicitation/initialElicitationDraft.js +27 -0
- package/dist/primitives/elicitation/initialElicitationDraft.js.map +1 -0
- package/dist/primitives/elicitation/prepareElicitationContent.d.ts +9 -0
- package/dist/primitives/elicitation/prepareElicitationContent.d.ts.map +1 -0
- package/dist/primitives/elicitation/prepareElicitationContent.js +46 -0
- package/dist/primitives/elicitation/prepareElicitationContent.js.map +1 -0
- package/dist/primitives/elicitation.d.ts +15 -0
- package/dist/primitives/elicitation.d.ts.map +1 -0
- package/dist/primitives/elicitation.js +27 -0
- package/dist/primitives/elicitation.js.map +1 -0
- package/dist/resources/McpManagerResource.d.ts.map +1 -1
- package/dist/resources/McpManagerResource.js +17 -3
- package/dist/resources/McpManagerResource.js.map +1 -1
- package/dist/resources/McpServerResource.d.ts +4 -0
- package/dist/resources/McpServerResource.d.ts.map +1 -1
- package/dist/resources/McpServerResource.js +153 -42
- package/dist/resources/McpServerResource.js.map +1 -1
- package/dist/resources/storage/McpLocalStorage.js +1 -1
- package/dist/resources/storage/McpLocalStorage.js.map +1 -1
- package/dist/resources/validateElicitationContent.d.ts +9 -0
- package/dist/resources/validateElicitationContent.d.ts.map +1 -0
- package/dist/resources/validateElicitationContent.js +47 -0
- package/dist/resources/validateElicitationContent.js.map +1 -0
- package/package.json +7 -6
- package/src/auth/createOAuthProvider.ts +2 -2
- package/src/auth/types.ts +1 -1
- package/src/index.ts +4 -0
- package/src/mcp-scope.ts +32 -0
- package/src/primitives/elicitation/McpElicitationPrimitiveAccept.tsx +48 -0
- package/src/primitives/elicitation/McpElicitationPrimitiveCancel.tsx +37 -0
- package/src/primitives/elicitation/McpElicitationPrimitiveDecline.tsx +37 -0
- package/src/primitives/elicitation/McpElicitationPrimitiveError.tsx +31 -0
- package/src/primitives/elicitation/McpElicitationPrimitiveFields.tsx +56 -0
- package/src/primitives/elicitation/McpElicitationPrimitiveItems.tsx +58 -0
- package/src/primitives/elicitation/McpElicitationPrimitiveMessage.tsx +26 -0
- package/src/primitives/elicitation/McpElicitationPrimitiveRoot.tsx +24 -0
- package/src/primitives/elicitation/context.tsx +45 -0
- package/src/primitives/elicitation/initialElicitationDraft.test.ts +34 -0
- package/src/primitives/elicitation/initialElicitationDraft.ts +39 -0
- package/src/primitives/elicitation/prepareElicitationContent.test.ts +415 -0
- package/src/primitives/elicitation/prepareElicitationContent.ts +116 -0
- package/src/primitives/elicitation.ts +14 -0
- package/src/resources/McpManagerResource.test.ts +173 -3
- package/src/resources/McpManagerResource.ts +37 -3
- package/src/resources/McpServerResource.test.ts +735 -4
- package/src/resources/McpServerResource.ts +237 -29
- package/src/resources/storage/McpLocalStorage.ts +1 -1
- package/src/resources/validateElicitationContent.test.ts +123 -0
- 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 = (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 ({
|
|
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]);
|