@assistant-ui/react-mcp 0.1.16 → 0.1.18

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 (61) hide show
  1. package/dist/auth/createOAuthProvider.d.ts +13 -2
  2. package/dist/auth/createOAuthProvider.d.ts.map +1 -1
  3. package/dist/auth/createOAuthProvider.js +109 -31
  4. package/dist/auth/createOAuthProvider.js.map +1 -1
  5. package/dist/auth/types.d.ts +5 -1
  6. package/dist/auth/types.d.ts.map +1 -1
  7. package/dist/primitives/addForm/McpAddFormAuthFields.js +71 -28
  8. package/dist/primitives/addForm/McpAddFormAuthFields.js.map +1 -1
  9. package/dist/primitives/addForm/McpAddFormError.d.ts.map +1 -1
  10. package/dist/primitives/addForm/McpAddFormError.js +30 -10
  11. package/dist/primitives/addForm/McpAddFormError.js.map +1 -1
  12. package/dist/primitives/addForm/McpAddFormNameField.js +31 -18
  13. package/dist/primitives/addForm/McpAddFormNameField.js.map +1 -1
  14. package/dist/primitives/addForm/McpAddFormRoot.d.ts.map +1 -1
  15. package/dist/primitives/addForm/McpAddFormRoot.js +32 -11
  16. package/dist/primitives/addForm/McpAddFormRoot.js.map +1 -1
  17. package/dist/primitives/addForm/McpAddFormUrlField.js +31 -18
  18. package/dist/primitives/addForm/McpAddFormUrlField.js.map +1 -1
  19. package/dist/primitives/addForm/context.d.ts +9 -1
  20. package/dist/primitives/addForm/context.d.ts.map +1 -1
  21. package/dist/primitives/addForm/context.js.map +1 -1
  22. package/dist/resources/McpManagerResource.d.ts.map +1 -1
  23. package/dist/resources/McpManagerResource.js +330 -247
  24. package/dist/resources/McpManagerResource.js.map +1 -1
  25. package/dist/resources/McpServerRemovalFence.d.ts +7 -0
  26. package/dist/resources/McpServerRemovalFence.d.ts.map +1 -0
  27. package/dist/resources/McpServerRemovalFence.js +11 -0
  28. package/dist/resources/McpServerRemovalFence.js.map +1 -0
  29. package/dist/resources/McpServerResource.d.ts.map +1 -1
  30. package/dist/resources/McpServerResource.js +41 -16
  31. package/dist/resources/McpServerResource.js.map +1 -1
  32. package/dist/resources/storage/McpLocalStorage.d.ts.map +1 -1
  33. package/dist/resources/storage/McpLocalStorage.js +16 -1
  34. package/dist/resources/storage/McpLocalStorage.js.map +1 -1
  35. package/dist/resources/storage/types.d.ts +10 -8
  36. package/dist/resources/storage/types.d.ts.map +1 -1
  37. package/dist/utils/serverUrl.d.ts +8 -0
  38. package/dist/utils/serverUrl.d.ts.map +1 -0
  39. package/dist/utils/serverUrl.js +15 -0
  40. package/dist/utils/serverUrl.js.map +1 -0
  41. package/package.json +7 -7
  42. package/src/auth/createOAuthProvider.test.ts +544 -35
  43. package/src/auth/createOAuthProvider.ts +201 -46
  44. package/src/auth/types.ts +5 -1
  45. package/src/primitives/addForm/McpAddFormAccessibility.test.tsx +261 -0
  46. package/src/primitives/addForm/McpAddFormAuthFields.tsx +33 -15
  47. package/src/primitives/addForm/McpAddFormError.tsx +17 -3
  48. package/src/primitives/addForm/McpAddFormNameField.tsx +13 -1
  49. package/src/primitives/addForm/McpAddFormRoot.tsx +51 -8
  50. package/src/primitives/addForm/McpAddFormUrlField.tsx +12 -1
  51. package/src/primitives/addForm/context.tsx +10 -0
  52. package/src/resources/McpManagerResource.test.ts +712 -9
  53. package/src/resources/McpManagerResource.ts +208 -56
  54. package/src/resources/McpServerRemovalFence.ts +21 -0
  55. package/src/resources/McpServerResource.test.ts +252 -17
  56. package/src/resources/McpServerResource.ts +40 -15
  57. package/src/resources/storage/McpLocalStorage.test.ts +28 -0
  58. package/src/resources/storage/McpLocalStorage.ts +23 -1
  59. package/src/resources/storage/types.ts +10 -8
  60. package/src/utils/serverUrl.test.ts +66 -0
  61. package/src/utils/serverUrl.ts +23 -0
@@ -14,7 +14,7 @@ export namespace McpAddFormPrimitiveAuthFields {
14
14
  export const McpAddFormPrimitiveAuthFields: FC<
15
15
  McpAddFormPrimitiveAuthFields.Props
16
16
  > = ({ children }) => {
17
- const { state, setField } = useAddForm();
17
+ const { state, ids, setField } = useAddForm();
18
18
 
19
19
  if (children) {
20
20
  const Render = children;
@@ -23,25 +23,43 @@ export const McpAddFormPrimitiveAuthFields: FC<
23
23
 
24
24
  if (state.authType === "bearer") {
25
25
  return (
26
- <input
27
- type="password"
28
- placeholder="Bearer token"
29
- value={state.bearerToken}
30
- onChange={(e) => setField("bearerToken", e.target.value)}
31
- data-mcp-auth-field="bearer-token"
32
- />
26
+ <div>
27
+ <label
28
+ htmlFor={ids.bearerToken}
29
+ data-mcp-auth-field-label="bearer-token"
30
+ >
31
+ Bearer token
32
+ </label>
33
+ <input
34
+ id={ids.bearerToken}
35
+ type="password"
36
+ value={state.bearerToken}
37
+ onChange={(e) => setField("bearerToken", e.target.value)}
38
+ aria-invalid={state.errorField === "bearerToken" ? true : undefined}
39
+ aria-describedby={
40
+ state.errorField === "bearerToken" ? ids.error : undefined
41
+ }
42
+ data-mcp-auth-field="bearer-token"
43
+ />
44
+ </div>
33
45
  );
34
46
  }
35
47
 
36
48
  if (state.authType === "oauth") {
37
49
  return (
38
- <input
39
- type="text"
40
- placeholder="Scopes (space-separated, optional)"
41
- value={state.scopes}
42
- onChange={(e) => setField("scopes", e.target.value)}
43
- data-mcp-auth-field="oauth-scopes"
44
- />
50
+ <div>
51
+ <label htmlFor={ids.scopes} data-mcp-auth-field-label="oauth-scopes">
52
+ OAuth scopes
53
+ </label>
54
+ <input
55
+ id={ids.scopes}
56
+ type="text"
57
+ placeholder="Scopes (space-separated, optional)"
58
+ value={state.scopes}
59
+ onChange={(e) => setField("scopes", e.target.value)}
60
+ data-mcp-auth-field="oauth-scopes"
61
+ />
62
+ </div>
45
63
  );
46
64
  }
47
65
 
@@ -2,6 +2,7 @@ import {
2
2
  type ComponentPropsWithoutRef,
3
3
  type ComponentRef,
4
4
  forwardRef,
5
+ isValidElement,
5
6
  } from "react";
6
7
  import { Primitive } from "@radix-ui/react-primitive";
7
8
  import { useAddForm } from "./context";
@@ -15,13 +16,26 @@ export const McpAddFormPrimitiveError = forwardRef<
15
16
  McpAddFormPrimitiveError.Element,
16
17
  McpAddFormPrimitiveError.Props
17
18
  >((props, ref) => {
18
- const { state } = useAddForm();
19
+ const { state, ids } = useAddForm();
19
20
  if (!state.error) return null;
20
- return (
21
- <Primitive.div {...props} ref={ref}>
21
+ const childId =
22
+ props.asChild && isValidElement<{ id?: string }>(props.children)
23
+ ? props.children.props.id
24
+ : undefined;
25
+ const customId = childId ?? props.id;
26
+ const needsDescriptionWrapper =
27
+ customId !== undefined && customId !== ids.error;
28
+ const error = (
29
+ <Primitive.div
30
+ {...props}
31
+ id={needsDescriptionWrapper ? props.id : ids.error}
32
+ role={props.role ?? "alert"}
33
+ ref={ref}
34
+ >
22
35
  {props.children ?? state.error}
23
36
  </Primitive.div>
24
37
  );
38
+ return needsDescriptionWrapper ? <div id={ids.error}>{error}</div> : error;
25
39
  });
26
40
 
27
41
  McpAddFormPrimitiveError.displayName = "McpAddFormPrimitive.Error";
@@ -18,12 +18,24 @@ export const McpAddFormPrimitiveNameField = forwardRef<
18
18
  McpAddFormPrimitiveNameField.Element,
19
19
  McpAddFormPrimitiveNameField.Props
20
20
  >((props, ref) => {
21
- const { state, setField } = useAddForm();
21
+ const { state, ids, setField } = useAddForm();
22
22
  return (
23
23
  <Primitive.input
24
24
  type="text"
25
25
  placeholder="Name"
26
26
  {...props}
27
+ aria-invalid={
28
+ props["aria-invalid"] ??
29
+ (state.errorField === "name" ? true : undefined)
30
+ }
31
+ aria-describedby={
32
+ [
33
+ props["aria-describedby"],
34
+ state.errorField === "name" ? ids.error : null,
35
+ ]
36
+ .filter(Boolean)
37
+ .join(" ") || undefined
38
+ }
27
39
  ref={ref}
28
40
  value={state.name}
29
41
  onChange={(e) => setField("name", e.target.value)}
@@ -4,12 +4,17 @@ import {
4
4
  type FormEventHandler,
5
5
  forwardRef,
6
6
  useCallback,
7
+ useId,
7
8
  useMemo,
8
9
  useState,
9
10
  } from "react";
10
11
  import { Primitive } from "@radix-ui/react-primitive";
11
12
  import { useAui } from "@assistant-ui/store";
12
- import { AddFormContext, type AddFormState } from "./context";
13
+ import {
14
+ AddFormContext,
15
+ type AddFormFieldIds,
16
+ type AddFormState,
17
+ } from "./context";
13
18
  import type { MCPAuthConfig } from "../../mcp-scope";
14
19
  import { invokeMcpCallback } from "../../utils/invokeMcpCallback";
15
20
 
@@ -21,6 +26,7 @@ const INITIAL: AddFormState = {
21
26
  scopes: "",
22
27
  submitting: false,
23
28
  error: null,
29
+ errorField: null,
24
30
  };
25
31
 
26
32
  function validateUrl(raw: string): { url: string } | { error: string } {
@@ -54,11 +60,30 @@ export const McpAddFormPrimitiveRoot = forwardRef<
54
60
  McpAddFormPrimitiveRoot.Props
55
61
  >(({ onSubmitted, onCancel, ...props }, ref) => {
56
62
  const aui = useAui();
63
+ const formId = useId();
57
64
  const [state, setState] = useState<AddFormState>(INITIAL);
65
+ const ids = useMemo<AddFormFieldIds>(
66
+ () => ({
67
+ bearerToken: `${formId}-bearer-token`,
68
+ scopes: `${formId}-oauth-scopes`,
69
+ error: `${formId}-error`,
70
+ }),
71
+ [formId],
72
+ );
58
73
 
59
74
  const setField = useCallback(
60
75
  <K extends keyof AddFormState>(key: K, value: AddFormState[K]) => {
61
- setState((prev) => ({ ...prev, [key]: value }));
76
+ setState((prev) => {
77
+ const clearsError =
78
+ prev.errorField === "form" ||
79
+ prev.errorField === key ||
80
+ (key === "authType" && prev.errorField === "bearerToken");
81
+ return {
82
+ ...prev,
83
+ [key]: value,
84
+ ...(clearsError ? { error: null, errorField: null } : {}),
85
+ };
86
+ });
62
87
  },
63
88
  [],
64
89
  );
@@ -87,19 +112,36 @@ export const McpAddFormPrimitiveRoot = forwardRef<
87
112
  const submit = useCallback(async () => {
88
113
  if (state.submitting) return;
89
114
  if (!state.name.trim()) {
90
- setState((p) => ({ ...p, error: "Name is required" }));
115
+ setState((p) => ({
116
+ ...p,
117
+ error: "Name is required",
118
+ errorField: "name",
119
+ }));
91
120
  return;
92
121
  }
93
122
  if (state.authType === "bearer" && !state.bearerToken.trim()) {
94
- setState((p) => ({ ...p, error: "Bearer token is required" }));
123
+ setState((p) => ({
124
+ ...p,
125
+ error: "Bearer token is required",
126
+ errorField: "bearerToken",
127
+ }));
95
128
  return;
96
129
  }
97
130
  const urlResult = validateUrl(state.url);
98
131
  if ("error" in urlResult) {
99
- setState((p) => ({ ...p, error: urlResult.error }));
132
+ setState((p) => ({
133
+ ...p,
134
+ error: urlResult.error,
135
+ errorField: "url",
136
+ }));
100
137
  return;
101
138
  }
102
- setState((p) => ({ ...p, submitting: true, error: null }));
139
+ setState((p) => ({
140
+ ...p,
141
+ submitting: true,
142
+ error: null,
143
+ errorField: null,
144
+ }));
103
145
  try {
104
146
  const id = await aui.mcp.addCustomServer({
105
147
  name: state.name.trim(),
@@ -113,6 +155,7 @@ export const McpAddFormPrimitiveRoot = forwardRef<
113
155
  ...p,
114
156
  submitting: false,
115
157
  error: err instanceof Error ? err.message : String(err),
158
+ errorField: "form",
116
159
  }));
117
160
  }
118
161
  }, [aui, buildAuth, onSubmitted, state]);
@@ -123,8 +166,8 @@ export const McpAddFormPrimitiveRoot = forwardRef<
123
166
  }, [onCancel]);
124
167
 
125
168
  const value = useMemo(
126
- () => ({ state, setField, reset, submit, cancel }),
127
- [state, setField, reset, submit, cancel],
169
+ () => ({ state, ids, setField, reset, submit, cancel }),
170
+ [state, ids, setField, reset, submit, cancel],
128
171
  );
129
172
 
130
173
  const onFormSubmit: FormEventHandler<HTMLFormElement> = (e) => {
@@ -18,13 +18,24 @@ export const McpAddFormPrimitiveUrlField = forwardRef<
18
18
  McpAddFormPrimitiveUrlField.Element,
19
19
  McpAddFormPrimitiveUrlField.Props
20
20
  >((props, ref) => {
21
- const { state, setField } = useAddForm();
21
+ const { state, ids, setField } = useAddForm();
22
22
  return (
23
23
  <Primitive.input
24
24
  type="url"
25
25
  inputMode="url"
26
26
  placeholder="https://example.com/mcp"
27
27
  {...props}
28
+ aria-invalid={
29
+ props["aria-invalid"] ?? (state.errorField === "url" ? true : undefined)
30
+ }
31
+ aria-describedby={
32
+ [
33
+ props["aria-describedby"],
34
+ state.errorField === "url" ? ids.error : null,
35
+ ]
36
+ .filter(Boolean)
37
+ .join(" ") || undefined
38
+ }
28
39
  ref={ref}
29
40
  value={state.url}
30
41
  onChange={(e) => setField("url", e.target.value)}
@@ -3,6 +3,14 @@ import type { MCPAuthConfig } from "../../mcp-scope";
3
3
 
4
4
  export type AddFormAuthType = MCPAuthConfig["type"];
5
5
 
6
+ export type AddFormErrorField = "name" | "url" | "bearerToken" | "form" | null;
7
+
8
+ export type AddFormFieldIds = {
9
+ bearerToken: string;
10
+ scopes: string;
11
+ error: string;
12
+ };
13
+
6
14
  export type AddFormState = {
7
15
  name: string;
8
16
  url: string;
@@ -11,10 +19,12 @@ export type AddFormState = {
11
19
  scopes: string;
12
20
  submitting: boolean;
13
21
  error: string | null;
22
+ errorField: AddFormErrorField;
14
23
  };
15
24
 
16
25
  export type AddFormContextValue = {
17
26
  state: AddFormState;
27
+ ids: AddFormFieldIds;
18
28
  setField: <K extends keyof AddFormState>(
19
29
  key: K,
20
30
  value: AddFormState[K],