@assistant-ui/react-mcp 0.1.18 → 0.1.20

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 (192) hide show
  1. package/dist/auth/buildHeaders.d.ts +3 -6
  2. package/dist/auth/buildHeaders.d.ts.map +1 -1
  3. package/dist/auth/createOAuthProvider.d.ts +24 -26
  4. package/dist/auth/createOAuthProvider.d.ts.map +1 -1
  5. package/dist/auth/types.d.ts +13 -16
  6. package/dist/auth/types.d.ts.map +1 -1
  7. package/dist/connector.d.ts +2 -5
  8. package/dist/connector.d.ts.map +1 -1
  9. package/dist/context/McpConnectorByIndexProvider.d.ts +3 -6
  10. package/dist/context/McpConnectorByIndexProvider.d.ts.map +1 -1
  11. package/dist/context/McpConnectorByIndexProvider.js +1 -1
  12. package/dist/context/McpCustomServerByIndexProvider.d.ts +3 -6
  13. package/dist/context/McpCustomServerByIndexProvider.d.ts.map +1 -1
  14. package/dist/context/McpCustomServerByIndexProvider.js +1 -1
  15. package/dist/context/McpServerByIdProvider.d.ts +3 -6
  16. package/dist/context/McpServerByIdProvider.d.ts.map +1 -1
  17. package/dist/context/McpServerByIdProvider.js +1 -1
  18. package/dist/hooks/useMcpOAuthCallback.d.ts +14 -17
  19. package/dist/hooks/useMcpOAuthCallback.d.ts.map +1 -1
  20. package/dist/hooks/useMcpOAuthCallback.js +6 -2
  21. package/dist/hooks/useMcpOAuthCallback.js.map +1 -1
  22. package/dist/index.d.ts +19 -18
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +4 -4
  25. package/dist/mcp-scope.d.ts +124 -127
  26. package/dist/mcp-scope.d.ts.map +1 -1
  27. package/dist/primitives/addForm/McpAddFormAuthFields.d.ts +14 -16
  28. package/dist/primitives/addForm/McpAddFormAuthFields.d.ts.map +1 -1
  29. package/dist/primitives/addForm/McpAddFormAuthFields.js +18 -71
  30. package/dist/primitives/addForm/McpAddFormAuthFields.js.map +1 -1
  31. package/dist/primitives/addForm/McpAddFormAuthSelect.d.ts +5 -8
  32. package/dist/primitives/addForm/McpAddFormAuthSelect.d.ts.map +1 -1
  33. package/dist/primitives/addForm/McpAddFormAuthSelect.js +1 -1
  34. package/dist/primitives/addForm/McpAddFormBearerTokenField.d.ts +8 -0
  35. package/dist/primitives/addForm/McpAddFormBearerTokenField.d.ts.map +1 -0
  36. package/dist/primitives/addForm/McpAddFormBearerTokenField.js +50 -0
  37. package/dist/primitives/addForm/McpAddFormBearerTokenField.js.map +1 -0
  38. package/dist/primitives/addForm/McpAddFormCancel.d.ts +6 -9
  39. package/dist/primitives/addForm/McpAddFormCancel.d.ts.map +1 -1
  40. package/dist/primitives/addForm/McpAddFormCancel.js +1 -1
  41. package/dist/primitives/addForm/McpAddFormError.d.ts +6 -9
  42. package/dist/primitives/addForm/McpAddFormError.d.ts.map +1 -1
  43. package/dist/primitives/addForm/McpAddFormError.js +1 -1
  44. package/dist/primitives/addForm/McpAddFormNameField.d.ts +5 -8
  45. package/dist/primitives/addForm/McpAddFormNameField.d.ts.map +1 -1
  46. package/dist/primitives/addForm/McpAddFormNameField.js +1 -1
  47. package/dist/primitives/addForm/McpAddFormRoot.d.ts +11 -14
  48. package/dist/primitives/addForm/McpAddFormRoot.d.ts.map +1 -1
  49. package/dist/primitives/addForm/McpAddFormRoot.js +2 -2
  50. package/dist/primitives/addForm/McpAddFormScopesField.d.ts +8 -0
  51. package/dist/primitives/addForm/McpAddFormScopesField.d.ts.map +1 -0
  52. package/dist/primitives/addForm/McpAddFormScopesField.js +38 -0
  53. package/dist/primitives/addForm/McpAddFormScopesField.js.map +1 -0
  54. package/dist/primitives/addForm/McpAddFormSubmit.d.ts +6 -9
  55. package/dist/primitives/addForm/McpAddFormSubmit.d.ts.map +1 -1
  56. package/dist/primitives/addForm/McpAddFormSubmit.js +1 -1
  57. package/dist/primitives/addForm/McpAddFormUrlField.d.ts +5 -8
  58. package/dist/primitives/addForm/McpAddFormUrlField.d.ts.map +1 -1
  59. package/dist/primitives/addForm/McpAddFormUrlField.js +1 -1
  60. package/dist/primitives/addForm/context.d.ts +25 -28
  61. package/dist/primitives/addForm/context.d.ts.map +1 -1
  62. package/dist/primitives/addForm.d.ts +10 -13
  63. package/dist/primitives/addForm.d.ts.map +1 -1
  64. package/dist/primitives/addForm.js +5 -1
  65. package/dist/primitives/addForm.js.map +1 -1
  66. package/dist/primitives/elicitation/McpElicitationPrimitiveAccept.d.ts +6 -9
  67. package/dist/primitives/elicitation/McpElicitationPrimitiveAccept.d.ts.map +1 -1
  68. package/dist/primitives/elicitation/McpElicitationPrimitiveAccept.js +2 -2
  69. package/dist/primitives/elicitation/McpElicitationPrimitiveCancel.d.ts +6 -9
  70. package/dist/primitives/elicitation/McpElicitationPrimitiveCancel.d.ts.map +1 -1
  71. package/dist/primitives/elicitation/McpElicitationPrimitiveCancel.js +2 -2
  72. package/dist/primitives/elicitation/McpElicitationPrimitiveDecline.d.ts +6 -9
  73. package/dist/primitives/elicitation/McpElicitationPrimitiveDecline.d.ts.map +1 -1
  74. package/dist/primitives/elicitation/McpElicitationPrimitiveDecline.js +2 -2
  75. package/dist/primitives/elicitation/McpElicitationPrimitiveError.d.ts +6 -9
  76. package/dist/primitives/elicitation/McpElicitationPrimitiveError.d.ts.map +1 -1
  77. package/dist/primitives/elicitation/McpElicitationPrimitiveError.js +1 -1
  78. package/dist/primitives/elicitation/McpElicitationPrimitiveFields.d.ts +8 -10
  79. package/dist/primitives/elicitation/McpElicitationPrimitiveFields.d.ts.map +1 -1
  80. package/dist/primitives/elicitation/McpElicitationPrimitiveFields.js +1 -1
  81. package/dist/primitives/elicitation/McpElicitationPrimitiveItems.d.ts +8 -11
  82. package/dist/primitives/elicitation/McpElicitationPrimitiveItems.d.ts.map +1 -1
  83. package/dist/primitives/elicitation/McpElicitationPrimitiveItems.js +2 -2
  84. package/dist/primitives/elicitation/McpElicitationPrimitiveMessage.d.ts +6 -9
  85. package/dist/primitives/elicitation/McpElicitationPrimitiveMessage.d.ts.map +1 -1
  86. package/dist/primitives/elicitation/McpElicitationPrimitiveMessage.js +1 -1
  87. package/dist/primitives/elicitation/McpElicitationPrimitiveRoot.d.ts +6 -9
  88. package/dist/primitives/elicitation/McpElicitationPrimitiveRoot.d.ts.map +1 -1
  89. package/dist/primitives/elicitation/McpElicitationPrimitiveRoot.js +1 -1
  90. package/dist/primitives/elicitation/context.d.ts +15 -18
  91. package/dist/primitives/elicitation/context.d.ts.map +1 -1
  92. package/dist/primitives/elicitation/initialElicitationDraft.d.ts +1 -4
  93. package/dist/primitives/elicitation/initialElicitationDraft.d.ts.map +1 -1
  94. package/dist/primitives/elicitation/prepareElicitationContent.d.ts +4 -7
  95. package/dist/primitives/elicitation/prepareElicitationContent.d.ts.map +1 -1
  96. package/dist/primitives/elicitation/prepareElicitationContent.js +5 -1
  97. package/dist/primitives/elicitation/prepareElicitationContent.js.map +1 -1
  98. package/dist/primitives/elicitation.d.ts +8 -14
  99. package/dist/primitives/elicitation.d.ts.map +1 -1
  100. package/dist/primitives/manager/McpManagerAddCustomTrigger.d.ts +6 -9
  101. package/dist/primitives/manager/McpManagerAddCustomTrigger.d.ts.map +1 -1
  102. package/dist/primitives/manager/McpManagerAddCustomTrigger.js +1 -1
  103. package/dist/primitives/manager/McpManagerConnectors.d.ts +14 -17
  104. package/dist/primitives/manager/McpManagerConnectors.d.ts.map +1 -1
  105. package/dist/primitives/manager/McpManagerConnectors.js +1 -1
  106. package/dist/primitives/manager/McpManagerCustomServers.d.ts +14 -17
  107. package/dist/primitives/manager/McpManagerCustomServers.d.ts.map +1 -1
  108. package/dist/primitives/manager/McpManagerCustomServers.js +1 -1
  109. package/dist/primitives/manager/McpManagerRoot.d.ts +6 -9
  110. package/dist/primitives/manager/McpManagerRoot.d.ts.map +1 -1
  111. package/dist/primitives/manager/McpManagerRoot.js +2 -2
  112. package/dist/primitives/manager.d.ts +4 -9
  113. package/dist/primitives/manager.d.ts.map +1 -1
  114. package/dist/primitives/server/McpServerConnectButton.d.ts +6 -9
  115. package/dist/primitives/server/McpServerConnectButton.d.ts.map +1 -1
  116. package/dist/primitives/server/McpServerConnectButton.js +2 -2
  117. package/dist/primitives/server/McpServerDisconnectButton.d.ts +6 -9
  118. package/dist/primitives/server/McpServerDisconnectButton.d.ts.map +1 -1
  119. package/dist/primitives/server/McpServerDisconnectButton.js +2 -2
  120. package/dist/primitives/server/McpServerError.d.ts +6 -9
  121. package/dist/primitives/server/McpServerError.d.ts.map +1 -1
  122. package/dist/primitives/server/McpServerError.js +2 -2
  123. package/dist/primitives/server/McpServerIcon.d.ts +12 -15
  124. package/dist/primitives/server/McpServerIcon.d.ts.map +1 -1
  125. package/dist/primitives/server/McpServerIcon.js +2 -2
  126. package/dist/primitives/server/McpServerName.d.ts +6 -9
  127. package/dist/primitives/server/McpServerName.d.ts.map +1 -1
  128. package/dist/primitives/server/McpServerName.js +2 -2
  129. package/dist/primitives/server/McpServerOAuthLink.d.ts +11 -14
  130. package/dist/primitives/server/McpServerOAuthLink.d.ts.map +1 -1
  131. package/dist/primitives/server/McpServerOAuthLink.js +2 -2
  132. package/dist/primitives/server/McpServerRemoveButton.d.ts +6 -9
  133. package/dist/primitives/server/McpServerRemoveButton.d.ts.map +1 -1
  134. package/dist/primitives/server/McpServerRemoveButton.js +2 -2
  135. package/dist/primitives/server/McpServerRoot.d.ts +6 -9
  136. package/dist/primitives/server/McpServerRoot.d.ts.map +1 -1
  137. package/dist/primitives/server/McpServerRoot.js +2 -2
  138. package/dist/primitives/server/McpServerStatus.d.ts +6 -9
  139. package/dist/primitives/server/McpServerStatus.d.ts.map +1 -1
  140. package/dist/primitives/server/McpServerStatus.js +2 -2
  141. package/dist/primitives/server/McpServerToolName.d.ts +6 -9
  142. package/dist/primitives/server/McpServerToolName.d.ts.map +1 -1
  143. package/dist/primitives/server/McpServerToolName.js +1 -1
  144. package/dist/primitives/server/McpServerTools.d.ts +8 -11
  145. package/dist/primitives/server/McpServerTools.d.ts.map +1 -1
  146. package/dist/primitives/server/McpServerTools.js +2 -2
  147. package/dist/primitives/server.d.ts +11 -16
  148. package/dist/primitives/server.d.ts.map +1 -1
  149. package/dist/resources/McpManagerResource.d.ts +13 -16
  150. package/dist/resources/McpManagerResource.d.ts.map +1 -1
  151. package/dist/resources/McpManagerResource.js +95 -64
  152. package/dist/resources/McpManagerResource.js.map +1 -1
  153. package/dist/resources/McpServerRemovalFence.d.ts +3 -6
  154. package/dist/resources/McpServerRemovalFence.d.ts.map +1 -1
  155. package/dist/resources/McpServerResource.d.ts +21 -24
  156. package/dist/resources/McpServerResource.d.ts.map +1 -1
  157. package/dist/resources/McpServerResource.js +55 -47
  158. package/dist/resources/McpServerResource.js.map +1 -1
  159. package/dist/resources/storage/McpCustomStorage.d.ts +2 -5
  160. package/dist/resources/storage/McpCustomStorage.d.ts.map +1 -1
  161. package/dist/resources/storage/McpLocalStorage.d.ts +18 -21
  162. package/dist/resources/storage/McpLocalStorage.d.ts.map +1 -1
  163. package/dist/resources/storage/McpLocalStorage.js +1 -1
  164. package/dist/resources/storage/McpMemoryStorage.d.ts +2 -5
  165. package/dist/resources/storage/McpMemoryStorage.d.ts.map +1 -1
  166. package/dist/resources/storage/types.d.ts +24 -27
  167. package/dist/resources/storage/types.d.ts.map +1 -1
  168. package/dist/resources/validateElicitationContent.d.ts +4 -6
  169. package/dist/resources/validateElicitationContent.d.ts.map +1 -1
  170. package/dist/utils/createMcpId.d.ts +1 -4
  171. package/dist/utils/createMcpId.d.ts.map +1 -1
  172. package/dist/utils/invokeMcpCallback.d.ts +1 -4
  173. package/dist/utils/invokeMcpCallback.d.ts.map +1 -1
  174. package/dist/utils/serverId.d.ts +2 -5
  175. package/dist/utils/serverId.d.ts.map +1 -1
  176. package/dist/utils/serverUrl.d.ts +4 -7
  177. package/dist/utils/serverUrl.d.ts.map +1 -1
  178. package/package.json +11 -10
  179. package/src/hooks/useMcpOAuthCallback.test.ts +53 -0
  180. package/src/hooks/useMcpOAuthCallback.tsx +12 -0
  181. package/src/index.ts +2 -1
  182. package/src/primitives/addForm/McpAddFormAccessibility.test.tsx +49 -0
  183. package/src/primitives/addForm/McpAddFormAuthFields.tsx +7 -15
  184. package/src/primitives/addForm/McpAddFormBearerTokenField.tsx +46 -0
  185. package/src/primitives/addForm/McpAddFormRoot.test.tsx +65 -0
  186. package/src/primitives/addForm/McpAddFormScopesField.tsx +34 -0
  187. package/src/primitives/addForm.ts +2 -0
  188. package/src/primitives/elicitation/prepareElicitationContent.ts +8 -6
  189. package/src/resources/McpManagerResource.test.ts +85 -0
  190. package/src/resources/McpManagerResource.ts +56 -23
  191. package/src/resources/McpServerResource.test.ts +121 -6
  192. package/src/resources/McpServerResource.ts +28 -12
@@ -1,9 +1,7 @@
1
- //#region src/resources/validateElicitationContent.d.ts
2
1
  type ValidationError = {
3
- property: string;
4
- message: string;
2
+ property: string;
3
+ message: string;
5
4
  };
6
- declare const validateElicitationContent: (requestedSchema: unknown, content: Record<string, unknown>) => readonly ValidationError[];
7
- //#endregion
8
- export { validateElicitationContent };
5
+ export declare const validateElicitationContent: (requestedSchema: unknown, content: Record<string, unknown>) => readonly ValidationError[];
6
+ export {};
9
7
  //# sourceMappingURL=validateElicitationContent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validateElicitationContent.d.ts","names":[],"sources":["../../src/resources/validateElicitationContent.ts"],"mappings":";KAAK;EACH;EACA;;cA+BW,6BACX,0BACA,SAAS,qCACC"}
1
+ {"version":3,"file":"validateElicitationContent.d.ts","sourceRoot":"","sources":["../../src/resources/validateElicitationContent.ts"],"names":[],"mappings":"AAAA,KAAK,eAAe,GAAG;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AA8BF,eAAO,MAAM,0BAA0B,GACrC,iBAAiB,OAAO,EACxB,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC/B,SAAS,eAAe,EA0C1B,CAAC"}
@@ -1,9 +1,6 @@
1
- //#region src/utils/createMcpId.d.ts
2
1
  /**
3
2
  * Both forms are alphanumeric or hyphenated, so neither can contain the
4
3
  * separators `assertValidServerId` reserves.
5
4
  */
6
- declare const createMcpId: () => string;
7
- //#endregion
8
- export { createMcpId };
5
+ export declare const createMcpId: () => string;
9
6
  //# sourceMappingURL=createMcpId.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"createMcpId.d.ts","names":[],"sources":["../../src/utils/createMcpId.ts"],"mappings":";;;;;cAMa"}
1
+ {"version":3,"file":"createMcpId.d.ts","sourceRoot":"","sources":["../../src/utils/createMcpId.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,eAAO,MAAM,WAAW,QAAO,MAGJ,CAAC"}
@@ -1,5 +1,2 @@
1
- //#region src/utils/invokeMcpCallback.d.ts
2
- declare const invokeMcpCallback: <TArgs extends unknown[]>(name: string, callback: ((...args: TArgs) => void) | undefined, ...args: TArgs) => void;
3
- //#endregion
4
- export { invokeMcpCallback };
1
+ export declare const invokeMcpCallback: <TArgs extends unknown[]>(name: string, callback: ((...args: TArgs) => void) | undefined, ...args: TArgs) => void;
5
2
  //# sourceMappingURL=invokeMcpCallback.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"invokeMcpCallback.d.ts","names":[],"sources":["../../src/utils/invokeMcpCallback.ts"],"mappings":";cAEa,oBAAqB,yBAChC,cACA,eAAe,MAAM,gCAClB,MAAM"}
1
+ {"version":3,"file":"invokeMcpCallback.d.ts","sourceRoot":"","sources":["../../src/utils/invokeMcpCallback.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,GAAI,KAAK,SAAS,OAAO,EAAE,EACvD,MAAM,MAAM,EACZ,UAAU,CAAC,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,SAAS,EAChD,GAAG,MAAM,KAAK,KACb,IAEF,CAAC"}
@@ -1,6 +1,3 @@
1
- //#region src/utils/serverId.d.ts
2
- declare function assertValidServerId(id: string): void;
3
- declare function assertUniqueServerIds(ids: Iterable<string>): void;
4
- //#endregion
5
- export { assertUniqueServerIds, assertValidServerId };
1
+ export declare function assertValidServerId(id: string): void;
2
+ export declare function assertUniqueServerIds(ids: Iterable<string>): void;
6
3
  //# sourceMappingURL=serverId.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serverId.d.ts","names":[],"sources":["../../src/utils/serverId.ts"],"mappings":";iBAIgB,oBAAoB;iBAgBpB,sBAAsB,KAAK"}
1
+ {"version":3,"file":"serverId.d.ts","sourceRoot":"","sources":["../../src/utils/serverId.ts"],"names":[],"mappings":"AAIA,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAcpD;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,CAUjE"}
@@ -1,8 +1,5 @@
1
- import { MCPPersistedAuthState } from "../auth/types.js";
2
- //#region src/utils/serverUrl.d.ts
3
- declare const normalizeMcpServerUrl: (serverUrl: string) => string;
4
- declare const isAuthStateForServerUrl: (state: MCPPersistedAuthState | null, serverUrl: string) => boolean;
5
- declare const hasPersistedCredentials: (state: MCPPersistedAuthState | null) => boolean;
6
- //#endregion
7
- export { hasPersistedCredentials, isAuthStateForServerUrl, normalizeMcpServerUrl };
1
+ import type { MCPPersistedAuthState } from "../auth/types.js";
2
+ export declare const normalizeMcpServerUrl: (serverUrl: string) => string;
3
+ export declare const isAuthStateForServerUrl: (state: MCPPersistedAuthState | null, serverUrl: string) => boolean;
4
+ export declare const hasPersistedCredentials: (state: MCPPersistedAuthState | null) => boolean;
8
5
  //# sourceMappingURL=serverUrl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serverUrl.d.ts","names":[],"sources":["../../src/utils/serverUrl.ts"],"mappings":";;cAEa,wBAAyB;cAGzB,0BACX,OAAO,8BACP;cAaW,0BACX,OAAO"}
1
+ {"version":3,"file":"serverUrl.d.ts","sourceRoot":"","sources":["../../src/utils/serverUrl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,yBAAsB;AAE3D,eAAO,MAAM,qBAAqB,GAAI,WAAW,MAAM,KAAG,MAC3B,CAAC;AAEhC,eAAO,MAAM,uBAAuB,GAClC,OAAO,qBAAqB,GAAG,IAAI,EACnC,WAAW,MAAM,KAChB,OAUF,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,OAAO,qBAAqB,GAAG,IAAI,KAClC,OAA0D,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react-mcp",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "MCP server configuration and connection primitives for @assistant-ui",
5
5
  "keywords": [
6
6
  "mcp",
@@ -27,13 +27,13 @@
27
27
  ],
28
28
  "sideEffects": false,
29
29
  "dependencies": {
30
- "@assistant-ui/core": "^0.3.19",
31
- "@assistant-ui/store": "^0.3.13",
32
- "@assistant-ui/tap": "^0.9.17",
30
+ "@assistant-ui/core": "^0.3.21",
31
+ "@assistant-ui/store": "^0.3.15",
32
+ "@assistant-ui/tap": "^0.9.19",
33
33
  "@modelcontextprotocol/client": "^2.0.0",
34
34
  "@modelcontextprotocol/core": "^2.0.0",
35
35
  "@radix-ui/react-primitive": "^2.1.10",
36
- "assistant-stream": "^0.3.43"
36
+ "assistant-stream": "^0.3.45"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@types/react": "*",
@@ -45,13 +45,13 @@
45
45
  }
46
46
  },
47
47
  "devDependencies": {
48
- "@assistant-ui/x-buildutils": "0.0.27",
49
- "@testing-library/dom": "^10.4.1",
48
+ "@assistant-ui/x-buildutils": "0.0.29",
49
+ "@testing-library/dom": "^10.4.2",
50
50
  "@testing-library/react": "^16.3.3",
51
- "@types/react": "^19.2.18",
51
+ "@types/react": "^19.3.0",
52
52
  "jsdom": "^30.0.1",
53
- "react": "^19.2.8",
54
- "vitest": "^5.0.0"
53
+ "react": "^19.3.0",
54
+ "vitest": "^5.0.1"
55
55
  },
56
56
  "publishConfig": {
57
57
  "access": "public",
@@ -69,6 +69,7 @@
69
69
  "scripts": {
70
70
  "build": "aui-build",
71
71
  "test": "vitest run",
72
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
72
73
  "test:watch": "vitest"
73
74
  }
74
75
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import {
4
4
  createElement,
5
+ StrictMode,
5
6
  Suspense,
6
7
  startTransition,
7
8
  type PropsWithChildren,
@@ -171,6 +172,58 @@ describe("useMcpOAuthCallback", () => {
171
172
  expect(onCompleteB).not.toHaveBeenCalled();
172
173
  expect(onCompleteA).toHaveBeenCalledWith("docs");
173
174
  });
175
+
176
+ it("keeps the newer callback's result when an older attempt settles late", async () => {
177
+ const secondUrl =
178
+ "https://app.example.com/oauth/callback?state=aui-mcp%3AZG9jcw.second";
179
+ let rejectFirst!: (err: Error) => void;
180
+ mocks.completeAuth.mockReturnValueOnce(
181
+ new Promise<void>((_resolve, reject) => {
182
+ rejectFirst = reject;
183
+ }),
184
+ );
185
+ mocks.completeAuth.mockResolvedValueOnce();
186
+ const onComplete = vi.fn();
187
+ const onError = vi.fn();
188
+
189
+ const { result, rerender } = renderHook(
190
+ ({ url }) => useMcpOAuthCallback({ url, onComplete, onError }),
191
+ { initialProps: { url: callbackUrl } },
192
+ );
193
+ await waitFor(() => expect(mocks.completeAuth).toHaveBeenCalledOnce());
194
+
195
+ rerender({ url: secondUrl });
196
+ await waitFor(() => expect(result.current.status).toBe("done"));
197
+
198
+ // The first attempt only fails once the second has already succeeded.
199
+ await act(async () => {
200
+ rejectFirst(new Error("authorization code expired"));
201
+ await Promise.resolve();
202
+ });
203
+
204
+ expect(result.current).toMatchObject({ status: "done", error: null });
205
+ expect(onError).not.toHaveBeenCalled();
206
+ expect(onComplete).toHaveBeenCalledExactlyOnceWith("docs");
207
+ });
208
+
209
+ // Strict Mode tears the effect down and remounts it without starting a new
210
+ // attempt, so the one already running must still publish its result.
211
+ it("publishes the result under Strict Mode's remount", async () => {
212
+ mocks.completeAuth.mockResolvedValueOnce();
213
+ const onComplete = vi.fn();
214
+
215
+ const { result } = renderHook(
216
+ () => useMcpOAuthCallback({ url: callbackUrl, onComplete }),
217
+ {
218
+ wrapper: ({ children }: PropsWithChildren) =>
219
+ createElement(StrictMode, null, children),
220
+ },
221
+ );
222
+
223
+ await waitFor(() => expect(result.current.status).toBe("done"));
224
+ expect(mocks.completeAuth).toHaveBeenCalledOnce();
225
+ expect(onComplete).toHaveBeenCalledExactlyOnceWith("docs");
226
+ });
174
227
  });
175
228
 
176
229
  describe("createMcpOAuthCallbackError", () => {
@@ -62,6 +62,15 @@ export function useMcpOAuthCallback(
62
62
  if (startedRef.current === url) return;
63
63
  startedRef.current = url;
64
64
 
65
+ // A callback page can be handed a second login URL while the first
66
+ // completeAuth is still in flight. That attempt keeps running, so it
67
+ // publishes only while its own URL is still the one being attempted;
68
+ // otherwise an older failure would overwrite the newer attempt's success.
69
+ // The check is against `startedRef` rather than a teardown flag so that
70
+ // Strict Mode's unmount-remount, which starts no new attempt, still
71
+ // publishes the result of the one already running.
72
+ const superseded = () => startedRef.current !== url;
73
+
65
74
  void (async () => {
66
75
  let serverId: string | null = null;
67
76
  try {
@@ -72,10 +81,13 @@ export function useMcpOAuthCallback(
72
81
  if (!serverId) {
73
82
  throw new Error("state was not created by assistant-ui MCP");
74
83
  }
84
+ if (superseded()) return;
75
85
  setResult({ status: "running", serverId, error: null });
76
86
  await aui.mcp.server({ id: serverId }).completeAuth(url);
87
+ if (superseded()) return;
77
88
  setResult({ status: "done", serverId, error: null });
78
89
  } catch (err) {
90
+ if (superseded()) return;
79
91
  const error = createMcpOAuthCallbackError(err, serverId);
80
92
  setResult({ status: "error", serverId, error });
81
93
  invokeMcpCallback("onError", optsRef.current.onError, error);
package/src/index.ts CHANGED
@@ -2,7 +2,8 @@
2
2
  import "./mcp-scope";
3
3
 
4
4
  // Resource — the entry point. Mount via:
5
- // useAui({ mcp: McpManagerResource({ connectors }) })
5
+ // const config = AuiConfig({ mcp: McpManagerResource({ connectors }) })
6
+ // <AssistantRuntimeProvider runtime={runtime} config={config}>…</AssistantRuntimeProvider>
6
7
  export {
7
8
  McpManagerResource,
8
9
  type McpManagerResourceProps,
@@ -22,6 +22,8 @@ import { McpAddFormPrimitiveNameField as NameField } from "./McpAddFormNameField
22
22
  import { McpAddFormPrimitiveUrlField as UrlField } from "./McpAddFormUrlField";
23
23
  import { McpAddFormPrimitiveAuthSelect as AuthSelect } from "./McpAddFormAuthSelect";
24
24
  import { McpAddFormPrimitiveAuthFields as AuthFields } from "./McpAddFormAuthFields";
25
+ import { McpAddFormPrimitiveBearerTokenField as BearerTokenField } from "./McpAddFormBearerTokenField";
26
+ import { McpAddFormPrimitiveScopesField as ScopesField } from "./McpAddFormScopesField";
25
27
  import { McpAddFormPrimitiveError as ErrorMessage } from "./McpAddFormError";
26
28
 
27
29
  const Form = ({ name = "Add server" }: { name?: string }) => (
@@ -122,6 +124,10 @@ describe("MCP add form accessibility", () => {
122
124
  Authentication method
123
125
  <AuthSelect />
124
126
  </label>
127
+ <label>
128
+ Permissions
129
+ <ScopesField />
130
+ </label>
125
131
  </Root>,
126
132
  );
127
133
  expect(screen.getByRole("textbox", { name: "Server name" })).toBeTruthy();
@@ -131,6 +137,7 @@ describe("MCP add form accessibility", () => {
131
137
  expect(
132
138
  screen.getByRole("combobox", { name: "Authentication method" }),
133
139
  ).toBeTruthy();
140
+ expect(screen.getByRole("textbox", { name: "Permissions" })).toBeTruthy();
134
141
  });
135
142
  it("preserves field labels and only marks the field with an error", () => {
136
143
  render(<Form />);
@@ -234,6 +241,48 @@ describe("MCP add form accessibility", () => {
234
241
  expect(field.getAttribute("aria-describedby")).toBe("hint");
235
242
  });
236
243
 
244
+ it("associates a missing token error with a custom bearer token field", () => {
245
+ render(
246
+ <Root aria-label="Add server">
247
+ <NameField aria-label="Name" />
248
+ <AuthSelect aria-label="Auth" />
249
+ <AuthFields>
250
+ {({ authType }) =>
251
+ authType === "bearer" ? (
252
+ <>
253
+ <label htmlFor="api-key">API key</label>
254
+ <BearerTokenField
255
+ id="api-key"
256
+ aria-describedby="api-key-hint"
257
+ />
258
+ <p id="api-key-hint">Paste the key from your dashboard.</p>
259
+ </>
260
+ ) : null
261
+ }
262
+ </AuthFields>
263
+ <ErrorMessage />
264
+ </Root>,
265
+ );
266
+ fireEvent.change(screen.getByRole("textbox", { name: "Name" }), {
267
+ target: { value: "Docs" },
268
+ });
269
+ fireEvent.change(screen.getByRole("combobox", { name: "Auth" }), {
270
+ target: { value: "bearer" },
271
+ });
272
+ const field = screen.getByLabelText("API key");
273
+ fireEvent.submit(screen.getByRole("form"));
274
+ const error = screen.getByRole("alert");
275
+ expect(error.textContent).toBe("Bearer token is required");
276
+ expect(field.getAttribute("aria-invalid")).toBe("true");
277
+ expect(field.getAttribute("aria-describedby")).toBe(
278
+ `api-key-hint ${error.id}`,
279
+ );
280
+ fireEvent.change(field, { target: { value: "secret" } });
281
+ expect(screen.queryByRole("alert")).toBeNull();
282
+ expect(field.hasAttribute("aria-invalid")).toBe(false);
283
+ expect(field.getAttribute("aria-describedby")).toBe("api-key-hint");
284
+ });
285
+
237
286
  it("keeps URL help text associated when validation errors appear and clear", () => {
238
287
  render(
239
288
  <Root aria-label="Add server">
@@ -1,11 +1,14 @@
1
1
  import type { FC } from "react";
2
+ import { McpAddFormPrimitiveBearerTokenField } from "./McpAddFormBearerTokenField";
3
+ import { McpAddFormPrimitiveScopesField } from "./McpAddFormScopesField";
2
4
  import { type AddFormAuthType, useAddForm } from "./context";
3
5
 
4
6
  export namespace McpAddFormPrimitiveAuthFields {
5
7
  export type Props = {
6
8
  /**
7
9
  * Optional render override. Receives the current auth type so apps can render
8
- * fully custom inputs. Defaults to a minimal built-in for bearer / oauth.
10
+ * fully custom inputs, bound to the form through `BearerTokenField` and
11
+ * `ScopesField`. Defaults to a minimal built-in for bearer / oauth.
9
12
  */
10
13
  children?: FC<{ authType: AddFormAuthType }>;
11
14
  };
@@ -14,7 +17,7 @@ export namespace McpAddFormPrimitiveAuthFields {
14
17
  export const McpAddFormPrimitiveAuthFields: FC<
15
18
  McpAddFormPrimitiveAuthFields.Props
16
19
  > = ({ children }) => {
17
- const { state, ids, setField } = useAddForm();
20
+ const { state, ids } = useAddForm();
18
21
 
19
22
  if (children) {
20
23
  const Render = children;
@@ -30,15 +33,8 @@ export const McpAddFormPrimitiveAuthFields: FC<
30
33
  >
31
34
  Bearer token
32
35
  </label>
33
- <input
36
+ <McpAddFormPrimitiveBearerTokenField
34
37
  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
38
  data-mcp-auth-field="bearer-token"
43
39
  />
44
40
  </div>
@@ -51,12 +47,8 @@ export const McpAddFormPrimitiveAuthFields: FC<
51
47
  <label htmlFor={ids.scopes} data-mcp-auth-field-label="oauth-scopes">
52
48
  OAuth scopes
53
49
  </label>
54
- <input
50
+ <McpAddFormPrimitiveScopesField
55
51
  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
52
  data-mcp-auth-field="oauth-scopes"
61
53
  />
62
54
  </div>
@@ -0,0 +1,46 @@
1
+ import {
2
+ type ComponentPropsWithoutRef,
3
+ type ComponentRef,
4
+ forwardRef,
5
+ } from "react";
6
+ import { Primitive } from "@radix-ui/react-primitive";
7
+ import { useAddForm } from "./context";
8
+
9
+ export namespace McpAddFormPrimitiveBearerTokenField {
10
+ export type Element = ComponentRef<typeof Primitive.input>;
11
+ export type Props = Omit<
12
+ ComponentPropsWithoutRef<typeof Primitive.input>,
13
+ "value" | "onChange" | "type"
14
+ >;
15
+ }
16
+
17
+ export const McpAddFormPrimitiveBearerTokenField = forwardRef<
18
+ McpAddFormPrimitiveBearerTokenField.Element,
19
+ McpAddFormPrimitiveBearerTokenField.Props
20
+ >((props, ref) => {
21
+ const { state, ids, setField } = useAddForm();
22
+ return (
23
+ <Primitive.input
24
+ type="password"
25
+ {...props}
26
+ aria-invalid={
27
+ props["aria-invalid"] ??
28
+ (state.errorField === "bearerToken" ? true : undefined)
29
+ }
30
+ aria-describedby={
31
+ [
32
+ props["aria-describedby"],
33
+ state.errorField === "bearerToken" ? ids.error : null,
34
+ ]
35
+ .filter(Boolean)
36
+ .join(" ") || undefined
37
+ }
38
+ ref={ref}
39
+ value={state.bearerToken}
40
+ onChange={(e) => setField("bearerToken", e.target.value)}
41
+ />
42
+ );
43
+ });
44
+
45
+ McpAddFormPrimitiveBearerTokenField.displayName =
46
+ "McpAddFormPrimitive.BearerTokenField";
@@ -7,6 +7,7 @@ import {
7
7
  screen,
8
8
  waitFor,
9
9
  } from "@testing-library/react";
10
+ import type { ComponentProps } from "react";
10
11
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
11
12
 
12
13
  const mocks = vi.hoisted(() => ({
@@ -20,12 +21,18 @@ vi.mock("@assistant-ui/store", async (importOriginal) => ({
20
21
  }),
21
22
  }));
22
23
 
24
+ import { McpAddFormPrimitiveAuthFields } from "./McpAddFormAuthFields";
25
+ import { McpAddFormPrimitiveAuthSelect } from "./McpAddFormAuthSelect";
26
+ import { McpAddFormPrimitiveBearerTokenField } from "./McpAddFormBearerTokenField";
23
27
  import { McpAddFormPrimitiveError } from "./McpAddFormError";
24
28
  import { McpAddFormPrimitiveNameField } from "./McpAddFormNameField";
25
29
  import { McpAddFormPrimitiveRoot } from "./McpAddFormRoot";
30
+ import { McpAddFormPrimitiveScopesField } from "./McpAddFormScopesField";
26
31
  import { McpAddFormPrimitiveSubmit } from "./McpAddFormSubmit";
27
32
  import { McpAddFormPrimitiveUrlField } from "./McpAddFormUrlField";
28
33
 
34
+ const CustomInput = (props: ComponentProps<"input">) => <input {...props} />;
35
+
29
36
  describe("McpAddFormPrimitiveRoot", () => {
30
37
  beforeEach(() => {
31
38
  mocks.addCustomServer.mockReset();
@@ -37,6 +44,64 @@ describe("McpAddFormPrimitiveRoot", () => {
37
44
  vi.restoreAllMocks();
38
45
  });
39
46
 
47
+ it.each([
48
+ [
49
+ "bearer",
50
+ McpAddFormPrimitiveBearerTokenField,
51
+ "secret",
52
+ { type: "bearer", token: "secret" },
53
+ ],
54
+ [
55
+ "oauth",
56
+ McpAddFormPrimitiveScopesField,
57
+ "read, write",
58
+ { type: "oauth", scopes: ["read", "write"] },
59
+ ],
60
+ ] as const)(
61
+ "submits a custom %s input bound through its field part",
62
+ async (authType, Field, value, auth) => {
63
+ render(
64
+ <McpAddFormPrimitiveRoot>
65
+ <McpAddFormPrimitiveNameField aria-label="Name" />
66
+ <McpAddFormPrimitiveUrlField aria-label="URL" />
67
+ <McpAddFormPrimitiveAuthSelect aria-label="Auth" />
68
+ <McpAddFormPrimitiveAuthFields>
69
+ {({ authType: current }) =>
70
+ current === authType ? (
71
+ <Field asChild>
72
+ <CustomInput aria-label="Credential" />
73
+ </Field>
74
+ ) : null
75
+ }
76
+ </McpAddFormPrimitiveAuthFields>
77
+ <McpAddFormPrimitiveError />
78
+ <McpAddFormPrimitiveSubmit>Submit</McpAddFormPrimitiveSubmit>
79
+ </McpAddFormPrimitiveRoot>,
80
+ );
81
+
82
+ fireEvent.change(screen.getByLabelText("Name"), {
83
+ target: { value: "Docs" },
84
+ });
85
+ fireEvent.change(screen.getByLabelText("URL"), {
86
+ target: { value: "https://example.com/mcp" },
87
+ });
88
+ fireEvent.change(screen.getByLabelText("Auth"), {
89
+ target: { value: authType },
90
+ });
91
+ fireEvent.change(screen.getByLabelText("Credential"), {
92
+ target: { value },
93
+ });
94
+ fireEvent.click(screen.getByRole("button", { name: "Submit" }));
95
+
96
+ await waitFor(() => expect(mocks.addCustomServer).toHaveBeenCalledOnce());
97
+ expect(mocks.addCustomServer).toHaveBeenCalledWith({
98
+ name: "Docs",
99
+ url: "https://example.com/mcp",
100
+ auth,
101
+ });
102
+ },
103
+ );
104
+
40
105
  it.each(["throws", "rejects"] as const)(
41
106
  "does not turn a successful add into an error when onSubmitted %s",
42
107
  async (mode) => {
@@ -0,0 +1,34 @@
1
+ import {
2
+ type ComponentPropsWithoutRef,
3
+ type ComponentRef,
4
+ forwardRef,
5
+ } from "react";
6
+ import { Primitive } from "@radix-ui/react-primitive";
7
+ import { useAddForm } from "./context";
8
+
9
+ export namespace McpAddFormPrimitiveScopesField {
10
+ export type Element = ComponentRef<typeof Primitive.input>;
11
+ export type Props = Omit<
12
+ ComponentPropsWithoutRef<typeof Primitive.input>,
13
+ "value" | "onChange" | "type"
14
+ >;
15
+ }
16
+
17
+ export const McpAddFormPrimitiveScopesField = forwardRef<
18
+ McpAddFormPrimitiveScopesField.Element,
19
+ McpAddFormPrimitiveScopesField.Props
20
+ >((props, ref) => {
21
+ const { state, setField } = useAddForm();
22
+ return (
23
+ <Primitive.input
24
+ type="text"
25
+ placeholder="Scopes (space-separated, optional)"
26
+ {...props}
27
+ ref={ref}
28
+ value={state.scopes}
29
+ onChange={(e) => setField("scopes", e.target.value)}
30
+ />
31
+ );
32
+ });
33
+
34
+ McpAddFormPrimitiveScopesField.displayName = "McpAddFormPrimitive.ScopesField";
@@ -3,6 +3,8 @@ export { McpAddFormPrimitiveNameField as NameField } from "./addForm/McpAddFormN
3
3
  export { McpAddFormPrimitiveUrlField as UrlField } from "./addForm/McpAddFormUrlField";
4
4
  export { McpAddFormPrimitiveAuthSelect as AuthSelect } from "./addForm/McpAddFormAuthSelect";
5
5
  export { McpAddFormPrimitiveAuthFields as AuthFields } from "./addForm/McpAddFormAuthFields";
6
+ export { McpAddFormPrimitiveBearerTokenField as BearerTokenField } from "./addForm/McpAddFormBearerTokenField";
7
+ export { McpAddFormPrimitiveScopesField as ScopesField } from "./addForm/McpAddFormScopesField";
6
8
  export { McpAddFormPrimitiveSubmit as Submit } from "./addForm/McpAddFormSubmit";
7
9
  export { McpAddFormPrimitiveCancel as Cancel } from "./addForm/McpAddFormCancel";
8
10
  export { McpAddFormPrimitiveError as Error } from "./addForm/McpAddFormError";
@@ -72,12 +72,14 @@ export const prepareElicitationContent = (
72
72
  const contentWithBooleanDefaults = {
73
73
  ...candidateContent,
74
74
  ...Object.fromEntries(
75
- missingRequiredBooleans.map(({ property }) => [
76
- property,
77
- typeof properties[property].default === "boolean"
78
- ? properties[property].default
79
- : false,
80
- ]),
75
+ missingRequiredBooleans.map(({ property }) => {
76
+ const schema = properties[property];
77
+ const defaultValue = isRecord(schema) ? schema.default : undefined;
78
+ return [
79
+ property,
80
+ typeof defaultValue === "boolean" ? defaultValue : false,
81
+ ];
82
+ }),
81
83
  ),
82
84
  };
83
85
  const validationErrors = validateElicitationContent(