@assistant-ui/react-mcp 0.0.21 → 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
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { initialElicitationDraft } from "./initialElicitationDraft";
|
|
5
|
+
|
|
6
|
+
describe("initialElicitationDraft", () => {
|
|
7
|
+
it("seeds defaults that match their declared types", () => {
|
|
8
|
+
expect(
|
|
9
|
+
initialElicitationDraft({
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {
|
|
12
|
+
enabled: { type: "boolean", default: true },
|
|
13
|
+
name: { type: "string", default: "Ada" },
|
|
14
|
+
ratio: { type: "number", default: 1.5 },
|
|
15
|
+
count: { type: "integer", default: 2 },
|
|
16
|
+
},
|
|
17
|
+
}),
|
|
18
|
+
).toEqual({ enabled: true, name: "Ada", ratio: 1.5, count: 2 });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("skips defaults that do not match their declared types", () => {
|
|
22
|
+
expect(
|
|
23
|
+
initialElicitationDraft({
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {
|
|
26
|
+
enabled: { type: "boolean", default: "true" },
|
|
27
|
+
name: { type: "string", default: 1 },
|
|
28
|
+
ratio: { type: "number", default: "1.5" },
|
|
29
|
+
count: { type: "integer", default: 1.5 },
|
|
30
|
+
},
|
|
31
|
+
}),
|
|
32
|
+
).toEqual({});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
2
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
|
|
4
|
+
const getOwn = (value: Record<string, unknown>, key: string): unknown =>
|
|
5
|
+
Object.hasOwn(value, key) ? value[key] : undefined;
|
|
6
|
+
|
|
7
|
+
const matchesType = (type: unknown, value: unknown): boolean => {
|
|
8
|
+
switch (type) {
|
|
9
|
+
case "string":
|
|
10
|
+
return typeof value === "string";
|
|
11
|
+
case "number":
|
|
12
|
+
return typeof value === "number";
|
|
13
|
+
case "integer":
|
|
14
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
15
|
+
case "boolean":
|
|
16
|
+
return typeof value === "boolean";
|
|
17
|
+
default:
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const initialElicitationDraft = (
|
|
23
|
+
requestedSchema: unknown,
|
|
24
|
+
): Record<string, unknown> => {
|
|
25
|
+
if (!isRecord(requestedSchema)) return {};
|
|
26
|
+
|
|
27
|
+
const properties = getOwn(requestedSchema, "properties");
|
|
28
|
+
if (!isRecord(properties)) return {};
|
|
29
|
+
|
|
30
|
+
return Object.fromEntries(
|
|
31
|
+
Object.keys(properties).flatMap((name) => {
|
|
32
|
+
const schema = properties[name];
|
|
33
|
+
if (!isRecord(schema) || !Object.hasOwn(schema, "default")) return [];
|
|
34
|
+
|
|
35
|
+
const value = schema.default;
|
|
36
|
+
return matchesType(getOwn(schema, "type"), value) ? [[name, value]] : [];
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { prepareElicitationContent } from "./prepareElicitationContent";
|
|
5
|
+
|
|
6
|
+
describe("prepareElicitationContent", () => {
|
|
7
|
+
it("accepts parseable number and integer draft values", () => {
|
|
8
|
+
expect(
|
|
9
|
+
prepareElicitationContent(
|
|
10
|
+
{
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
count: { type: "integer" },
|
|
14
|
+
ratio: { type: "number" },
|
|
15
|
+
enabled: { type: "boolean" },
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
{ count: "42", ratio: "1.5", enabled: false },
|
|
19
|
+
).content,
|
|
20
|
+
).toEqual({ count: 42, ratio: 1.5, enabled: false });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("flags and excludes an unparseable number value", () => {
|
|
24
|
+
expect(
|
|
25
|
+
prepareElicitationContent(
|
|
26
|
+
{
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: { count: { type: "number" } },
|
|
29
|
+
},
|
|
30
|
+
{ count: "not a number" },
|
|
31
|
+
).content,
|
|
32
|
+
).toEqual({});
|
|
33
|
+
expect(
|
|
34
|
+
prepareElicitationContent(
|
|
35
|
+
{
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: { count: { type: "number" } },
|
|
38
|
+
},
|
|
39
|
+
{ count: "not a number" },
|
|
40
|
+
).invalid,
|
|
41
|
+
).toEqual(["count"]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("flags and excludes a fractional integer value", () => {
|
|
45
|
+
expect(
|
|
46
|
+
prepareElicitationContent(
|
|
47
|
+
{
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: { count: { type: "integer" } },
|
|
50
|
+
},
|
|
51
|
+
{ count: "1.5" },
|
|
52
|
+
).content,
|
|
53
|
+
).toEqual({});
|
|
54
|
+
expect(
|
|
55
|
+
prepareElicitationContent(
|
|
56
|
+
{
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: { count: { type: "integer" } },
|
|
59
|
+
},
|
|
60
|
+
{ count: "1.5" },
|
|
61
|
+
).invalid,
|
|
62
|
+
).toEqual(["count"]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("flags enum values outside the allowed values for the Accept gate", () => {
|
|
66
|
+
expect(
|
|
67
|
+
prepareElicitationContent(
|
|
68
|
+
{
|
|
69
|
+
type: "object",
|
|
70
|
+
properties: { color: { enum: ["red", "blue"] } },
|
|
71
|
+
},
|
|
72
|
+
{ color: "green" },
|
|
73
|
+
),
|
|
74
|
+
).toEqual({ content: {}, missingRequired: [], invalid: ["color"] });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("does not read inherited draft values", () => {
|
|
78
|
+
expect(
|
|
79
|
+
prepareElicitationContent(
|
|
80
|
+
{
|
|
81
|
+
type: "object",
|
|
82
|
+
required: ["toString"],
|
|
83
|
+
properties: { toString: { type: "string" } },
|
|
84
|
+
},
|
|
85
|
+
{},
|
|
86
|
+
),
|
|
87
|
+
).toEqual({
|
|
88
|
+
content: {},
|
|
89
|
+
missingRequired: ["toString"],
|
|
90
|
+
invalid: [],
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("seeds required booleans with a true schema default", () => {
|
|
95
|
+
expect(
|
|
96
|
+
prepareElicitationContent(
|
|
97
|
+
{
|
|
98
|
+
type: "object",
|
|
99
|
+
required: ["enabled"],
|
|
100
|
+
properties: {
|
|
101
|
+
enabled: { type: "boolean", default: true },
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{},
|
|
105
|
+
),
|
|
106
|
+
).toEqual({
|
|
107
|
+
content: { enabled: true },
|
|
108
|
+
missingRequired: [],
|
|
109
|
+
invalid: [],
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("seeds required booleans with a false schema default", () => {
|
|
114
|
+
expect(
|
|
115
|
+
prepareElicitationContent(
|
|
116
|
+
{
|
|
117
|
+
type: "object",
|
|
118
|
+
required: ["enabled"],
|
|
119
|
+
properties: {
|
|
120
|
+
enabled: { type: "boolean", default: false },
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{},
|
|
124
|
+
),
|
|
125
|
+
).toEqual({
|
|
126
|
+
content: { enabled: false },
|
|
127
|
+
missingRequired: [],
|
|
128
|
+
invalid: [],
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("seeds required booleans without a schema default as false", () => {
|
|
133
|
+
expect(
|
|
134
|
+
prepareElicitationContent(
|
|
135
|
+
{
|
|
136
|
+
type: "object",
|
|
137
|
+
required: ["enabled"],
|
|
138
|
+
properties: { enabled: { type: "boolean" } },
|
|
139
|
+
},
|
|
140
|
+
{},
|
|
141
|
+
),
|
|
142
|
+
).toEqual({
|
|
143
|
+
content: { enabled: false },
|
|
144
|
+
missingRequired: [],
|
|
145
|
+
invalid: [],
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("omits absent optional booleans, including schema defaults", () => {
|
|
150
|
+
expect(
|
|
151
|
+
prepareElicitationContent(
|
|
152
|
+
{
|
|
153
|
+
type: "object",
|
|
154
|
+
properties: {
|
|
155
|
+
enabled: { type: "boolean" },
|
|
156
|
+
sendEmail: { type: "boolean", default: true },
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{},
|
|
160
|
+
),
|
|
161
|
+
).toEqual({ content: {}, missingRequired: [], invalid: [] });
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("seeds required booleans with an empty draft value as false", () => {
|
|
165
|
+
expect(
|
|
166
|
+
prepareElicitationContent(
|
|
167
|
+
{
|
|
168
|
+
type: "object",
|
|
169
|
+
required: ["enabled"],
|
|
170
|
+
properties: { enabled: { type: "boolean" } },
|
|
171
|
+
},
|
|
172
|
+
{ enabled: "" },
|
|
173
|
+
),
|
|
174
|
+
).toEqual({
|
|
175
|
+
content: { enabled: false },
|
|
176
|
+
missingRequired: [],
|
|
177
|
+
invalid: [],
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("omits an empty optional string draft", () => {
|
|
182
|
+
expect(
|
|
183
|
+
prepareElicitationContent(
|
|
184
|
+
{
|
|
185
|
+
type: "object",
|
|
186
|
+
properties: { query: { type: "string" } },
|
|
187
|
+
},
|
|
188
|
+
{ query: "" },
|
|
189
|
+
),
|
|
190
|
+
).toEqual({ content: {}, missingRequired: [], invalid: [] });
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("treats an empty required string draft as absent", () => {
|
|
194
|
+
expect(
|
|
195
|
+
prepareElicitationContent(
|
|
196
|
+
{
|
|
197
|
+
type: "object",
|
|
198
|
+
required: ["query"],
|
|
199
|
+
properties: { query: { type: "string" } },
|
|
200
|
+
},
|
|
201
|
+
{ query: "" },
|
|
202
|
+
),
|
|
203
|
+
).toEqual({
|
|
204
|
+
content: {},
|
|
205
|
+
missingRequired: ["query"],
|
|
206
|
+
invalid: [],
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("treats a cleared enum draft as absent instead of invalid", () => {
|
|
211
|
+
expect(
|
|
212
|
+
prepareElicitationContent(
|
|
213
|
+
{
|
|
214
|
+
type: "object",
|
|
215
|
+
properties: {
|
|
216
|
+
color: { type: "string", enum: ["red", "blue"] },
|
|
217
|
+
shade: { enum: ["light", "dark"] },
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
{ color: "", shade: "" },
|
|
221
|
+
),
|
|
222
|
+
).toEqual({ content: {}, missingRequired: [], invalid: [] });
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("ignores empty-string defaults and enums on non-string schemas", () => {
|
|
226
|
+
expect(
|
|
227
|
+
prepareElicitationContent(
|
|
228
|
+
{
|
|
229
|
+
type: "object",
|
|
230
|
+
properties: {
|
|
231
|
+
limit: { type: "number", default: "" },
|
|
232
|
+
count: { type: "integer", enum: [1, 2, ""] },
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
{ limit: "", count: "" },
|
|
236
|
+
),
|
|
237
|
+
).toEqual({ content: {}, missingRequired: [], invalid: [] });
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("seeds a required boolean whose schema declares an empty-string default", () => {
|
|
241
|
+
expect(
|
|
242
|
+
prepareElicitationContent(
|
|
243
|
+
{
|
|
244
|
+
type: "object",
|
|
245
|
+
required: ["enabled"],
|
|
246
|
+
properties: { enabled: { type: "boolean", default: "" } },
|
|
247
|
+
},
|
|
248
|
+
{ enabled: "" },
|
|
249
|
+
),
|
|
250
|
+
).toEqual({
|
|
251
|
+
content: { enabled: false },
|
|
252
|
+
missingRequired: [],
|
|
253
|
+
invalid: [],
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("reports a cleared required enum draft as missing instead of invalid", () => {
|
|
258
|
+
expect(
|
|
259
|
+
prepareElicitationContent(
|
|
260
|
+
{
|
|
261
|
+
type: "object",
|
|
262
|
+
required: ["color"],
|
|
263
|
+
properties: { color: { type: "string", enum: ["red", "blue"] } },
|
|
264
|
+
},
|
|
265
|
+
{ color: "" },
|
|
266
|
+
),
|
|
267
|
+
).toEqual({ content: {}, missingRequired: ["color"], invalid: [] });
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("keeps an empty string the enum admits", () => {
|
|
271
|
+
expect(
|
|
272
|
+
prepareElicitationContent(
|
|
273
|
+
{
|
|
274
|
+
type: "object",
|
|
275
|
+
required: ["note"],
|
|
276
|
+
properties: { note: { type: "string", enum: ["", "none"] } },
|
|
277
|
+
},
|
|
278
|
+
{ note: "" },
|
|
279
|
+
),
|
|
280
|
+
).toEqual({ content: { note: "" }, missingRequired: [], invalid: [] });
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("ignores an empty-string default the enum does not admit", () => {
|
|
284
|
+
expect(
|
|
285
|
+
prepareElicitationContent(
|
|
286
|
+
{
|
|
287
|
+
type: "object",
|
|
288
|
+
required: ["color"],
|
|
289
|
+
properties: {
|
|
290
|
+
color: { type: "string", enum: ["red", "blue"], default: "" },
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
{ color: "" },
|
|
294
|
+
),
|
|
295
|
+
).toEqual({ content: {}, missingRequired: ["color"], invalid: [] });
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("keeps an empty string the schema defaults to", () => {
|
|
299
|
+
expect(
|
|
300
|
+
prepareElicitationContent(
|
|
301
|
+
{
|
|
302
|
+
type: "object",
|
|
303
|
+
required: ["prefix"],
|
|
304
|
+
properties: { prefix: { type: "string", default: "" } },
|
|
305
|
+
},
|
|
306
|
+
{ prefix: "" },
|
|
307
|
+
),
|
|
308
|
+
).toEqual({ content: { prefix: "" }, missingRequired: [], invalid: [] });
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("treats an empty optional boolean draft as absent", () => {
|
|
312
|
+
expect(
|
|
313
|
+
prepareElicitationContent(
|
|
314
|
+
{
|
|
315
|
+
type: "object",
|
|
316
|
+
properties: { enabled: { type: "boolean" } },
|
|
317
|
+
},
|
|
318
|
+
{ enabled: "" },
|
|
319
|
+
),
|
|
320
|
+
).toEqual({ content: {}, missingRequired: [], invalid: [] });
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("treats undefined and empty optional boolean drafts as absent", () => {
|
|
324
|
+
expect(
|
|
325
|
+
prepareElicitationContent(
|
|
326
|
+
{
|
|
327
|
+
type: "object",
|
|
328
|
+
properties: {
|
|
329
|
+
enabled: { type: "boolean" },
|
|
330
|
+
sendEmail: { type: "boolean" },
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
{ enabled: undefined, sendEmail: "" },
|
|
334
|
+
),
|
|
335
|
+
).toEqual({ content: {}, missingRequired: [], invalid: [] });
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("flags and excludes mistyped boolean draft values", () => {
|
|
339
|
+
expect(
|
|
340
|
+
prepareElicitationContent(
|
|
341
|
+
{
|
|
342
|
+
type: "object",
|
|
343
|
+
properties: { enabled: { type: "boolean" } },
|
|
344
|
+
},
|
|
345
|
+
{ enabled: "true" },
|
|
346
|
+
),
|
|
347
|
+
).toEqual({ content: {}, missingRequired: [], invalid: ["enabled"] });
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it("flags a mistyped required boolean instead of seeding false", () => {
|
|
351
|
+
expect(
|
|
352
|
+
prepareElicitationContent(
|
|
353
|
+
{
|
|
354
|
+
type: "object",
|
|
355
|
+
properties: { enabled: { type: "boolean" } },
|
|
356
|
+
required: ["enabled"],
|
|
357
|
+
},
|
|
358
|
+
{ enabled: "true" },
|
|
359
|
+
),
|
|
360
|
+
).toEqual({ content: {}, missingRequired: [], invalid: ["enabled"] });
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("treats null drafts as absent for booleans and required fields", () => {
|
|
364
|
+
expect(
|
|
365
|
+
prepareElicitationContent(
|
|
366
|
+
{
|
|
367
|
+
type: "object",
|
|
368
|
+
properties: {
|
|
369
|
+
enabled: { type: "boolean" },
|
|
370
|
+
query: { type: "string" },
|
|
371
|
+
},
|
|
372
|
+
required: ["enabled", "query"],
|
|
373
|
+
},
|
|
374
|
+
{ enabled: null, query: null },
|
|
375
|
+
),
|
|
376
|
+
).toEqual({
|
|
377
|
+
content: { enabled: false },
|
|
378
|
+
missingRequired: ["query"],
|
|
379
|
+
invalid: [],
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("treats a cleared numeric draft as absent instead of invalid", () => {
|
|
384
|
+
expect(
|
|
385
|
+
prepareElicitationContent(
|
|
386
|
+
{
|
|
387
|
+
type: "object",
|
|
388
|
+
properties: {
|
|
389
|
+
limit: { type: "number" },
|
|
390
|
+
count: { type: "integer" },
|
|
391
|
+
},
|
|
392
|
+
required: ["limit"],
|
|
393
|
+
},
|
|
394
|
+
{ limit: "", count: "" },
|
|
395
|
+
),
|
|
396
|
+
).toEqual({
|
|
397
|
+
content: {},
|
|
398
|
+
missingRequired: ["limit"],
|
|
399
|
+
invalid: [],
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it("reports required draft values that resolve to absent", () => {
|
|
404
|
+
expect(
|
|
405
|
+
prepareElicitationContent(
|
|
406
|
+
{
|
|
407
|
+
type: "object",
|
|
408
|
+
required: ["name", "email", "ignored"],
|
|
409
|
+
properties: {},
|
|
410
|
+
},
|
|
411
|
+
{ name: "", email: "ada@example.com" },
|
|
412
|
+
).missingRequired,
|
|
413
|
+
).toEqual(["name", "ignored"]);
|
|
414
|
+
});
|
|
415
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { validateElicitationContent } from "../../resources/validateElicitationContent";
|
|
2
|
+
|
|
3
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
4
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
|
|
6
|
+
const coerceValue = (schema: unknown, value: unknown): unknown => {
|
|
7
|
+
if (!isRecord(schema) || typeof value !== "string") {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
if (schema.type !== "number" && schema.type !== "integer") {
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const number = Number(value);
|
|
15
|
+
if (value.trim() === "") return value;
|
|
16
|
+
if (!Number.isFinite(number)) return value;
|
|
17
|
+
if (schema.type === "integer" && !Number.isInteger(number)) {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
return number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const isBooleanSchema = (schema: unknown): schema is Record<string, unknown> =>
|
|
24
|
+
isRecord(schema) && schema.type === "boolean";
|
|
25
|
+
|
|
26
|
+
const isNonStringTypedSchema = (schema: Record<string, unknown>): boolean =>
|
|
27
|
+
schema.type === "boolean" ||
|
|
28
|
+
schema.type === "number" ||
|
|
29
|
+
schema.type === "integer";
|
|
30
|
+
|
|
31
|
+
const schemaAdmitsEmptyString = (schema: unknown): boolean => {
|
|
32
|
+
if (!isRecord(schema) || isNonStringTypedSchema(schema)) return false;
|
|
33
|
+
|
|
34
|
+
const values = Object.hasOwn(schema, "enum") ? schema.enum : undefined;
|
|
35
|
+
if (Array.isArray(values)) return values.includes("");
|
|
36
|
+
|
|
37
|
+
return Object.hasOwn(schema, "default") && schema.default === "";
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const isAbsentDraftValue = (schema: unknown, value: unknown) =>
|
|
41
|
+
value === undefined ||
|
|
42
|
+
value === null ||
|
|
43
|
+
(value === "" && !schemaAdmitsEmptyString(schema));
|
|
44
|
+
|
|
45
|
+
export const prepareElicitationContent = (
|
|
46
|
+
requestedSchema: unknown,
|
|
47
|
+
draft: Record<string, unknown>,
|
|
48
|
+
): {
|
|
49
|
+
content: Record<string, unknown>;
|
|
50
|
+
missingRequired: readonly string[];
|
|
51
|
+
invalid: readonly string[];
|
|
52
|
+
} => {
|
|
53
|
+
const properties =
|
|
54
|
+
isRecord(requestedSchema) && isRecord(requestedSchema.properties)
|
|
55
|
+
? requestedSchema.properties
|
|
56
|
+
: {};
|
|
57
|
+
const candidateContent = Object.fromEntries(
|
|
58
|
+
Object.entries(draft).flatMap(([name, value]) => {
|
|
59
|
+
const schema = properties[name];
|
|
60
|
+
if (isAbsentDraftValue(schema, value)) return [];
|
|
61
|
+
return [[name, coerceValue(schema, value)]];
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
const missingRequiredBooleans = validateElicitationContent(
|
|
65
|
+
requestedSchema,
|
|
66
|
+
candidateContent,
|
|
67
|
+
).filter(
|
|
68
|
+
({ property }) =>
|
|
69
|
+
isBooleanSchema(properties[property]) &&
|
|
70
|
+
!Object.hasOwn(candidateContent, property),
|
|
71
|
+
);
|
|
72
|
+
const contentWithBooleanDefaults = {
|
|
73
|
+
...candidateContent,
|
|
74
|
+
...Object.fromEntries(
|
|
75
|
+
missingRequiredBooleans.map(({ property }) => [
|
|
76
|
+
property,
|
|
77
|
+
typeof properties[property].default === "boolean"
|
|
78
|
+
? properties[property].default
|
|
79
|
+
: false,
|
|
80
|
+
]),
|
|
81
|
+
),
|
|
82
|
+
};
|
|
83
|
+
const validationErrors = validateElicitationContent(
|
|
84
|
+
requestedSchema,
|
|
85
|
+
contentWithBooleanDefaults,
|
|
86
|
+
);
|
|
87
|
+
const missingRequired = [
|
|
88
|
+
...new Set(
|
|
89
|
+
validationErrors
|
|
90
|
+
.filter(
|
|
91
|
+
({ property }) =>
|
|
92
|
+
!Object.hasOwn(contentWithBooleanDefaults, property),
|
|
93
|
+
)
|
|
94
|
+
.map(({ property }) => property),
|
|
95
|
+
),
|
|
96
|
+
];
|
|
97
|
+
const invalid = [
|
|
98
|
+
...new Set(
|
|
99
|
+
validationErrors
|
|
100
|
+
.filter(({ property }) =>
|
|
101
|
+
Object.hasOwn(contentWithBooleanDefaults, property),
|
|
102
|
+
)
|
|
103
|
+
.map(({ property }) => property),
|
|
104
|
+
),
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
content: Object.fromEntries(
|
|
109
|
+
Object.entries(contentWithBooleanDefaults).filter(
|
|
110
|
+
([property]) => !invalid.includes(property),
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
missingRequired,
|
|
114
|
+
invalid,
|
|
115
|
+
};
|
|
116
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
McpElicitationPrimitiveItems as Items,
|
|
3
|
+
useMcpElicitation,
|
|
4
|
+
} from "./elicitation/McpElicitationPrimitiveItems";
|
|
5
|
+
export { McpElicitationPrimitiveRoot as Root } from "./elicitation/McpElicitationPrimitiveRoot";
|
|
6
|
+
export { McpElicitationPrimitiveMessage as Message } from "./elicitation/McpElicitationPrimitiveMessage";
|
|
7
|
+
export { McpElicitationPrimitiveError as Error } from "./elicitation/McpElicitationPrimitiveError";
|
|
8
|
+
export {
|
|
9
|
+
McpElicitationPrimitiveFields as Fields,
|
|
10
|
+
useMcpElicitationField,
|
|
11
|
+
} from "./elicitation/McpElicitationPrimitiveFields";
|
|
12
|
+
export { McpElicitationPrimitiveAccept as Accept } from "./elicitation/McpElicitationPrimitiveAccept";
|
|
13
|
+
export { McpElicitationPrimitiveDecline as Decline } from "./elicitation/McpElicitationPrimitiveDecline";
|
|
14
|
+
export { McpElicitationPrimitiveCancel as Cancel } from "./elicitation/McpElicitationPrimitiveCancel";
|