@assistant-ui/react-mcp 0.1.13 → 0.1.15
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/hooks/useMcpOAuthCallback.js.map +1 -1
- package/dist/primitives/server/McpServerIcon.js.map +1 -1
- package/dist/primitives/server/McpServerOAuthLink.js.map +1 -1
- package/dist/resources/McpManagerResource.d.ts.map +1 -1
- package/dist/resources/McpManagerResource.js +2 -1
- package/dist/resources/McpManagerResource.js.map +1 -1
- package/dist/resources/McpServerResource.d.ts.map +1 -1
- package/dist/resources/McpServerResource.js +60 -13
- package/dist/resources/McpServerResource.js.map +1 -1
- package/dist/utils/createMcpId.d.ts +9 -0
- package/dist/utils/createMcpId.d.ts.map +1 -0
- package/dist/utils/createMcpId.js +11 -0
- package/dist/utils/createMcpId.js.map +1 -0
- package/dist/utils/invokeMcpCallback.d.ts.map +1 -1
- package/dist/utils/invokeMcpCallback.js +2 -12
- package/dist/utils/invokeMcpCallback.js.map +1 -1
- package/package.json +7 -7
- package/src/resources/McpManagerResource.test.ts +128 -0
- package/src/resources/McpManagerResource.ts +2 -4
- package/src/resources/McpServerResource.test.ts +43 -2
- package/src/resources/McpServerResource.ts +91 -21
- package/src/utils/createMcpId.test.ts +25 -0
- package/src/utils/createMcpId.ts +10 -0
- package/src/utils/invokeMcpCallback.ts +3 -21
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMcpOAuthCallback.js","names":["FC","ReactNode","useEffect","useRef","useState","useAui","decodeServerIdFromState","invokeMcpCallback","createMcpOAuthCallbackError","err","serverId","Error","message","String","cause","UseMcpOAuthCallbackOptions","url","onComplete","onError","UseMcpOAuthCallbackResult","status","error","useMcpOAuthCallback","opts","aui","result","setResult","startedRef","optsRef","current","window","location","href","parsed","URL","state","searchParams","get","mcp","server","id","completeAuth","McpOAuthCallback","children","t0","$","_c","t1","t2"],"sources":["../../src/hooks/useMcpOAuthCallback.tsx"],"sourcesContent":["import { type FC, type ReactNode, useEffect, useRef, useState } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { decodeServerIdFromState } from \"../auth/createOAuthProvider\";\nimport { invokeMcpCallback } from \"../utils/invokeMcpCallback\";\n\nexport const createMcpOAuthCallbackError = (\n err: unknown,\n serverId: string | null,\n): Error => {\n const message = err instanceof Error ? err.message : String(err);\n if (serverId) {\n return new Error(\n `MCP OAuth callback for server \"${serverId}\" failed: ${message}`,\n { cause: err },\n );\n }\n return new Error(`MCP OAuth callback failed: ${message}`, { cause: err });\n};\n\nexport type UseMcpOAuthCallbackOptions = {\n /** Defaults to `window.location.href`. */\n url?: string;\n onComplete?: (serverId: string) => void;\n onError?: (err: Error) => void;\n};\n\nexport type UseMcpOAuthCallbackResult = {\n status: \"idle\" | \"running\" | \"done\" | \"error\";\n serverId: string | null;\n error: Error | null;\n};\n\nexport function useMcpOAuthCallback(\n opts: UseMcpOAuthCallbackOptions = {},\n): UseMcpOAuthCallbackResult {\n const aui = useAui();\n const [result, setResult] = useState<UseMcpOAuthCallbackResult>({\n status: \"idle\",\n serverId: null,\n error: null,\n });\n // Guard against React 18 Strict Mode's mount-unmount-remount: the effect\n // body must not run completeAuth twice for the same URL, otherwise the\n // single-use OAuth code is double-redeemed and the second attempt 4xxs.\n const startedRef = useRef<string | null>(null);\n const optsRef = useRef(opts);\n optsRef.current = opts;\n\n useEffect(() => {\n const url =\n opts.url ?? (typeof window !== \"undefined\" ? window.location.href : null);\n if (!url) return;\n if (startedRef.current === url) return;\n startedRef.current = url;\n\n void (async () => {\n let serverId: string | null = null;\n try {\n const parsed = new URL(url);\n const state = parsed.searchParams.get(\"state\");\n if (state) serverId = decodeServerIdFromState(state);\n const error = parsed.searchParams.get(\"error\");\n if (error) {\n throw new Error(\n parsed.searchParams.get(\"error_description\") ?? error,\n );\n }\n if (!state) throw new Error('missing \"state\" parameter');\n if (!serverId) {\n throw new Error(\"state was not created by assistant-ui MCP\");\n }\n setResult({ status: \"running\", serverId, error: null });\n await aui.mcp.server({ id: serverId }).completeAuth(url);\n setResult({ status: \"done\", serverId, error: null });\n } catch (err) {\n const error = createMcpOAuthCallbackError(err, serverId);\n setResult({ status: \"error\", serverId, error });\n invokeMcpCallback(\"onError\", optsRef.current.onError, error);\n return;\n }\n\n invokeMcpCallback(\"onComplete\", optsRef.current.onComplete, serverId);\n })();\n }, [aui, opts.url]);\n\n return result;\n}\n\nexport const McpOAuthCallback: FC<\n UseMcpOAuthCallbackOptions & {\n children?: (result: UseMcpOAuthCallbackResult) => ReactNode;\n }\n> = ({ children, ...opts }) => {\n const result = useMcpOAuthCallback(opts);\n if (children) return <>{children(result)}</>;\n return null;\n};\n"],"mappings":";;;;;;;AAKA,MAAaQ,+BACXC,KACAC,aACU;CACV,MAAME,UAAUH,eAAeE,QAAQF,IAAIG,UAAUC,OAAOJ,GAAG;CAC/D,IAAIC,UACF,OAAO,IAAIC,MACT,kCAAkCD,SAAQ,YAAaE,WACvD,EAAEE,OAAOL,IAAI,CACf;CAEF,OAAO,IAAIE,MAAM,8BAA8BC,WAAW,EAAEE,OAAOL,IAAI,CAAC;AAC1E;AAeA,SAAgBa,oBACdC,OAAmC,CAAC,GACT;CAC3B,MAAMC,MAAMnB,OAAO;CACnB,MAAM,CAACoB,QAAQC,aAAatB,SAAoC;EAC9DgB,QAAQ;EACRV,UAAU;EACVW,OAAO;CACT,CAAC;CAID,MAAMM,aAAaxB,OAAsB,IAAI;CAC7C,MAAMyB,UAAUzB,OAAOoB,IAAI;CAC3BK,QAAQC,UAAUN;CAElBrB,gBAAgB;EACd,MAAMc,MACJO,KAAKP,QAAQ,OAAOc,WAAW,cAAcA,OAAOC,SAASC,OAAO;EACtE,IAAI,CAAChB,KAAK;EACV,IAAIW,WAAWE,YAAYb,KAAK;EAChCW,WAAWE,UAAUb;EAErB,CAAM,YAAY;GAChB,IAAIN,WAA0B;GAC9B,IAAI;IACF,MAAMuB,SAAS,IAAIC,IAAIlB,GAAG;IAC1B,MAAMmB,QAAQF,OAAOG,aAAaC,IAAI,OAAO;IAC7C,IAAIF,OAAOzB,WAAWJ,wBAAwB6B,KAAK;IACnD,MAAMd,UAAQY,OAAOG,aAAaC,IAAI,OAAO;IAC7C,IAAIhB,SACF,MAAM,IAAIV,MACRsB,OAAOG,aAAaC,IAAI,mBAAmB,KAAKhB,OAClD;IAEF,IAAI,CAACc,OAAO,MAAM,IAAIxB,MAAM,6BAA2B;IACvD,IAAI,CAACD,UACH,MAAM,IAAIC,MAAM,2CAA2C;IAE7De,UAAU;KAAEN,QAAQ;KAAWV;KAAUW,OAAO;IAAK,CAAC;IACtD,MAAMG,IAAIc,IAAIC,OAAO,EAAEC,IAAI9B,SAAS,CAAC,CAAC,CAAC+B,aAAazB,GAAG;IACvDU,UAAU;KAAEN,QAAQ;KAAQV;KAAUW,OAAO;IAAK,CAAC;GACrD,SAASZ,KAAK;IACZ,MAAMY,QAAQb,4BAA4BC,KAAKC,QAAQ;IACvDgB,UAAU;KAAEN,QAAQ;KAASV;KAAUW;IAAM,CAAC;IAC9Cd,kBAAkB,WAAWqB,QAAQC,QAAQX,SAASG,KAAK;IAC3D;GACF;GAEAd,kBAAkB,cAAcqB,QAAQC,QAAQZ,YAAYP,QAAQ;EACtE,EAAA,CAAG;CACL,GAAG,CAACc,KAAKD,KAAKP,GAAG,CAAC;CAElB,OAAOS;AACT;AAEA,MAAaiB,oBAITE,OAAA;CAAA,MAAAC,IAAAC,EAAA,CAAA;CAAA,IAAAH;CAAA,IAAApB;CAAA,IAAAsB,EAAA,OAAAD,IAAA;EAAC,CAAA,CAAAD,
|
|
1
|
+
{"version":3,"file":"useMcpOAuthCallback.js","names":["FC","ReactNode","useEffect","useRef","useState","useAui","decodeServerIdFromState","invokeMcpCallback","createMcpOAuthCallbackError","err","serverId","Error","message","String","cause","UseMcpOAuthCallbackOptions","url","onComplete","onError","UseMcpOAuthCallbackResult","status","error","useMcpOAuthCallback","opts","aui","result","setResult","startedRef","optsRef","current","window","location","href","parsed","URL","state","searchParams","get","mcp","server","id","completeAuth","McpOAuthCallback","children","t0","$","_c","t1","t2"],"sources":["../../src/hooks/useMcpOAuthCallback.tsx"],"sourcesContent":["import { type FC, type ReactNode, useEffect, useRef, useState } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { decodeServerIdFromState } from \"../auth/createOAuthProvider\";\nimport { invokeMcpCallback } from \"../utils/invokeMcpCallback\";\n\nexport const createMcpOAuthCallbackError = (\n err: unknown,\n serverId: string | null,\n): Error => {\n const message = err instanceof Error ? err.message : String(err);\n if (serverId) {\n return new Error(\n `MCP OAuth callback for server \"${serverId}\" failed: ${message}`,\n { cause: err },\n );\n }\n return new Error(`MCP OAuth callback failed: ${message}`, { cause: err });\n};\n\nexport type UseMcpOAuthCallbackOptions = {\n /** Defaults to `window.location.href`. */\n url?: string;\n onComplete?: (serverId: string) => void;\n onError?: (err: Error) => void;\n};\n\nexport type UseMcpOAuthCallbackResult = {\n status: \"idle\" | \"running\" | \"done\" | \"error\";\n serverId: string | null;\n error: Error | null;\n};\n\nexport function useMcpOAuthCallback(\n opts: UseMcpOAuthCallbackOptions = {},\n): UseMcpOAuthCallbackResult {\n const aui = useAui();\n const [result, setResult] = useState<UseMcpOAuthCallbackResult>({\n status: \"idle\",\n serverId: null,\n error: null,\n });\n // Guard against React 18 Strict Mode's mount-unmount-remount: the effect\n // body must not run completeAuth twice for the same URL, otherwise the\n // single-use OAuth code is double-redeemed and the second attempt 4xxs.\n const startedRef = useRef<string | null>(null);\n const optsRef = useRef(opts);\n optsRef.current = opts;\n\n useEffect(() => {\n const url =\n opts.url ?? (typeof window !== \"undefined\" ? window.location.href : null);\n if (!url) return;\n if (startedRef.current === url) return;\n startedRef.current = url;\n\n void (async () => {\n let serverId: string | null = null;\n try {\n const parsed = new URL(url);\n const state = parsed.searchParams.get(\"state\");\n if (state) serverId = decodeServerIdFromState(state);\n const error = parsed.searchParams.get(\"error\");\n if (error) {\n throw new Error(\n parsed.searchParams.get(\"error_description\") ?? error,\n );\n }\n if (!state) throw new Error('missing \"state\" parameter');\n if (!serverId) {\n throw new Error(\"state was not created by assistant-ui MCP\");\n }\n setResult({ status: \"running\", serverId, error: null });\n await aui.mcp.server({ id: serverId }).completeAuth(url);\n setResult({ status: \"done\", serverId, error: null });\n } catch (err) {\n const error = createMcpOAuthCallbackError(err, serverId);\n setResult({ status: \"error\", serverId, error });\n invokeMcpCallback(\"onError\", optsRef.current.onError, error);\n return;\n }\n\n invokeMcpCallback(\"onComplete\", optsRef.current.onComplete, serverId);\n })();\n }, [aui, opts.url]);\n\n return result;\n}\n\nexport const McpOAuthCallback: FC<\n UseMcpOAuthCallbackOptions & {\n children?: (result: UseMcpOAuthCallbackResult) => ReactNode;\n }\n> = ({ children, ...opts }) => {\n const result = useMcpOAuthCallback(opts);\n if (children) return <>{children(result)}</>;\n return null;\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpServerIcon.js","names":["ComponentPropsWithoutRef","ComponentRef","forwardRef","Primitive","useAuiState","McpServerPrimitiveIcon","Element","img","Props","Omit","src","alt","t0","ref","$","_c","props","iconUrl","_temp","name","_temp2","effective","t1","t2","displayName","s","mcpServer","icon","s_0"],"sources":["../../../src/primitives/server/McpServerIcon.tsx"],"sourcesContent":["import {\n type ComponentPropsWithoutRef,\n type ComponentRef,\n forwardRef,\n} from \"react\";\nimport { Primitive } from \"@radix-ui/react-primitive\";\nimport { useAuiState } from \"@assistant-ui/store\";\n\nexport namespace McpServerPrimitiveIcon {\n export type Element = ComponentRef<typeof Primitive.img>;\n export type Props = Omit<\n ComponentPropsWithoutRef<typeof Primitive.img>,\n \"src\" | \"alt\"\n > & {\n /** Override the icon URL. Defaults to the server's icon prop. */\n src?: string;\n alt?: string;\n };\n}\n\nexport const McpServerPrimitiveIcon = forwardRef<\n McpServerPrimitiveIcon.Element,\n McpServerPrimitiveIcon.Props\n>(({ src, alt, ...props }, ref) => {\n const iconUrl = useAuiState((s) => s.mcpServer.icon ?? null);\n const name = useAuiState((s) => s.mcpServer.name);\n const effective = src ?? iconUrl;\n if (!effective) return null;\n return (\n <Primitive.img {...props} ref={ref} src={effective} alt={alt ?? name} />\n );\n});\n\nMcpServerPrimitiveIcon.displayName = \"McpServerPrimitive.Icon\";\n"],"mappings":";;;;;;AAoBA,MAAaK,yBAAyBH,YAGpCU,IAAAC,QAAA;CAAA,MAAAC,IAAAC,EAAA,CAAA;CAAA,IAAAJ;CAAA,IAAAK;CAAA,IAAAN;CAAA,IAAAI,EAAA,OAAAF,IAAA;EAAC,CAAA,CAAAF,KAAAC,
|
|
1
|
+
{"version":3,"file":"McpServerIcon.js","names":["ComponentPropsWithoutRef","ComponentRef","forwardRef","Primitive","useAuiState","McpServerPrimitiveIcon","Element","img","Props","Omit","src","alt","t0","ref","$","_c","props","iconUrl","_temp","name","_temp2","effective","t1","t2","displayName","s","mcpServer","icon","s_0"],"sources":["../../../src/primitives/server/McpServerIcon.tsx"],"sourcesContent":["import {\n type ComponentPropsWithoutRef,\n type ComponentRef,\n forwardRef,\n} from \"react\";\nimport { Primitive } from \"@radix-ui/react-primitive\";\nimport { useAuiState } from \"@assistant-ui/store\";\n\nexport namespace McpServerPrimitiveIcon {\n export type Element = ComponentRef<typeof Primitive.img>;\n export type Props = Omit<\n ComponentPropsWithoutRef<typeof Primitive.img>,\n \"src\" | \"alt\"\n > & {\n /** Override the icon URL. Defaults to the server's icon prop. */\n src?: string;\n alt?: string;\n };\n}\n\nexport const McpServerPrimitiveIcon = forwardRef<\n McpServerPrimitiveIcon.Element,\n McpServerPrimitiveIcon.Props\n>(({ src, alt, ...props }, ref) => {\n const iconUrl = useAuiState((s) => s.mcpServer.icon ?? null);\n const name = useAuiState((s) => s.mcpServer.name);\n const effective = src ?? iconUrl;\n if (!effective) return null;\n return (\n <Primitive.img {...props} ref={ref} src={effective} alt={alt ?? name} />\n );\n});\n\nMcpServerPrimitiveIcon.displayName = \"McpServerPrimitive.Icon\";\n"],"mappings":";;;;;;AAoBA,MAAaK,yBAAyBH,YAGpCU,IAAAC,QAAA;CAAA,MAAAC,IAAAC,EAAA,CAAA;CAAA,IAAAJ;CAAA,IAAAK;CAAA,IAAAN;CAAA,IAAAI,EAAA,OAAAF,IAAA;EAAC,CAAA,CAAAF,KAAAC,KAAA,GAAAK,SAAAJ;EAAsBE,EAAA,KAAAF;EAAAE,EAAA,KAAAH;EAAAG,EAAA,KAAAE;EAAAF,EAAA,KAAAJ;CAAA,OAAA;EAAAC,MAAAG,EAAA;EAAAE,QAAAF,EAAA;EAAAJ,MAAAI,EAAA;CAAA;CACvB,MAAAG,UAAgBb,YAAYc,KAA+B;CAC3D,MAAAC,OAAaf,YAAYgB,MAAuB;CAChD,MAAAC,YAAkBX,OAAAO;CAClB,IAAI,CAACI,WAAS,OAAS;CAEoC,MAAAC,KAAAX,OAAAQ;CAAW,IAAAI;CAAA,IAAAT,EAAA,OAAAO,aAAAP,EAAA,OAAAE,SAAAF,EAAA,OAAAD,OAAAC,EAAA,OAAAQ,IAAA;EAApEC,KAAA,oBAAA,UAAA,KAAA;GAAA,GAAmBP;GAAYH;GAAUQ,KAAAA;GAAgB,KAAAC;EAAW,CAAA;EAAIR,EAAA,KAAAO;EAAAP,EAAA,KAAAE;EAAAF,EAAA,KAAAD;EAAAC,EAAA,KAAAQ;EAAAR,EAAA,KAAAS;CAAA,OAAAA,KAAAT,EAAA;CAAA,OAAxES;AAAwE,CAE3E;AAEDlB,uBAAuBmB,cAAc;AAVnC,SAAAN,MAAAO,GAAA;CAAA,OACmCA,EAACC,UAAUC,QAAX;AAAwB;AAD3D,SAAAP,OAAAQ,KAAA;CAAA,OAEgCH,IAACC,UAAUP;AAAK"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpServerOAuthLink.js","names":["ComponentPropsWithoutRef","ComponentRef","forwardRef","Primitive","useAuiState","McpServerPrimitiveOAuthLink","Element","a","Props","Omit","href","t0","ref","$","_c","props","url","_temp","effective","t1","rel","t2","displayName","s","mcpServer","authorizationUrl"],"sources":["../../../src/primitives/server/McpServerOAuthLink.tsx"],"sourcesContent":["import {\n type ComponentPropsWithoutRef,\n type ComponentRef,\n forwardRef,\n} from \"react\";\nimport { Primitive } from \"@radix-ui/react-primitive\";\nimport { useAuiState } from \"@assistant-ui/store\";\n\nexport namespace McpServerPrimitiveOAuthLink {\n export type Element = ComponentRef<typeof Primitive.a>;\n export type Props = Omit<\n ComponentPropsWithoutRef<typeof Primitive.a>,\n \"href\"\n > & {\n /** Optional href override; defaults to the server's authorizationUrl. */\n href?: string;\n };\n}\n\nexport const McpServerPrimitiveOAuthLink = forwardRef<\n McpServerPrimitiveOAuthLink.Element,\n McpServerPrimitiveOAuthLink.Props\n>(({ href, ...props }, ref) => {\n const url = useAuiState((s) => s.mcpServer.authorizationUrl);\n const effective = href ?? url;\n if (!effective) return null;\n return (\n <Primitive.a\n target=\"_blank\"\n {...props}\n ref={ref}\n href={effective}\n rel={props.rel ?? \"noopener noreferrer\"}\n />\n );\n});\n\nMcpServerPrimitiveOAuthLink.displayName = \"McpServerPrimitive.OAuthLink\";\n"],"mappings":";;;;;;AAmBA,MAAaK,8BAA8BH,YAGzCS,IAAAC,QAAA;CAAA,MAAAC,IAAAC,EAAA,CAAA;CAAA,IAAAJ;CAAA,IAAAK;CAAA,IAAAF,EAAA,OAAAF,IAAA;EAAC,CAAA,CAAAD,
|
|
1
|
+
{"version":3,"file":"McpServerOAuthLink.js","names":["ComponentPropsWithoutRef","ComponentRef","forwardRef","Primitive","useAuiState","McpServerPrimitiveOAuthLink","Element","a","Props","Omit","href","t0","ref","$","_c","props","url","_temp","effective","t1","rel","t2","displayName","s","mcpServer","authorizationUrl"],"sources":["../../../src/primitives/server/McpServerOAuthLink.tsx"],"sourcesContent":["import {\n type ComponentPropsWithoutRef,\n type ComponentRef,\n forwardRef,\n} from \"react\";\nimport { Primitive } from \"@radix-ui/react-primitive\";\nimport { useAuiState } from \"@assistant-ui/store\";\n\nexport namespace McpServerPrimitiveOAuthLink {\n export type Element = ComponentRef<typeof Primitive.a>;\n export type Props = Omit<\n ComponentPropsWithoutRef<typeof Primitive.a>,\n \"href\"\n > & {\n /** Optional href override; defaults to the server's authorizationUrl. */\n href?: string;\n };\n}\n\nexport const McpServerPrimitiveOAuthLink = forwardRef<\n McpServerPrimitiveOAuthLink.Element,\n McpServerPrimitiveOAuthLink.Props\n>(({ href, ...props }, ref) => {\n const url = useAuiState((s) => s.mcpServer.authorizationUrl);\n const effective = href ?? url;\n if (!effective) return null;\n return (\n <Primitive.a\n target=\"_blank\"\n {...props}\n ref={ref}\n href={effective}\n rel={props.rel ?? \"noopener noreferrer\"}\n />\n );\n});\n\nMcpServerPrimitiveOAuthLink.displayName = \"McpServerPrimitive.OAuthLink\";\n"],"mappings":";;;;;;AAmBA,MAAaK,8BAA8BH,YAGzCS,IAAAC,QAAA;CAAA,MAAAC,IAAAC,EAAA,CAAA;CAAA,IAAAJ;CAAA,IAAAK;CAAA,IAAAF,EAAA,OAAAF,IAAA;EAAC,CAAA,CAAAD,MAAA,GAAAK,SAAAJ;EAAkBE,EAAA,KAAAF;EAAAE,EAAA,KAAAH;EAAAG,EAAA,KAAAE;CAAA,OAAA;EAAAL,OAAAG,EAAA;EAAAE,QAAAF,EAAA;CAAA;CACnB,MAAAG,MAAYZ,YAAYa,KAAmC;CAC3D,MAAAC,YAAkBR,QAAAM;CAClB,IAAI,CAACE,WAAS,OAAS;CAOd,MAAAC,KAAAJ,MAAKK,OAAL;CAAkC,IAAAC;CAAA,IAAAR,EAAA,OAAAK,aAAAL,EAAA,OAAAE,SAAAF,EAAA,OAAAD,OAAAC,EAAA,OAAAM,IAAA;EALzCE,KAAA,oBAAA,UAAA,GAAA;GACS,QAAA;GAAQ,GACXN;GACCH;GACCM,MAAAA;GACD,KAAAC;EAAkC,CAAA;EACvCN,EAAA,KAAAK;EAAAL,EAAA,KAAAE;EAAAF,EAAA,KAAAD;EAAAC,EAAA,KAAAM;EAAAN,EAAA,KAAAQ;CAAA,OAAAA,KAAAR,EAAA;CAAA,OANFQ;AAME,CAEL;AAEDhB,4BAA4BiB,cAAc;AAfxC,SAAAL,MAAAM,GAAA;CAAA,OAC+BA,EAACC,UAAUC;AAAiB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpManagerResource.d.ts","names":[],"sources":["../../src/resources/McpManagerResource.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"McpManagerResource.d.ts","names":[],"sources":["../../src/resources/McpManagerResource.ts"],"mappings":";;;;KAuBY;EACV,aAAa;EACb,UAAU;;EAEV;;EAEA;;EAEA;;cAyRW,gDAAkB,SAAA,sBAAA,OAAA"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assertUniqueServerIds } from "../utils/serverId.js";
|
|
2
|
+
import { createMcpId } from "../utils/createMcpId.js";
|
|
2
3
|
import { McpServerResource } from "./McpServerResource.js";
|
|
3
4
|
import { McpLocalStorage } from "./storage/McpLocalStorage.js";
|
|
4
5
|
import { attachTransformScopes, useAssistantClientRef, useClientLookup } from "@assistant-ui/store";
|
|
@@ -318,7 +319,7 @@ const useMcpManagerResource = (props) => {
|
|
|
318
319
|
t22 = async (t23) => {
|
|
319
320
|
const { name, url, auth, connectionTimeout: connectionTimeout_0, cache, elicitation } = t23;
|
|
320
321
|
const record = {
|
|
321
|
-
id:
|
|
322
|
+
id: createMcpId(),
|
|
322
323
|
name,
|
|
323
324
|
url,
|
|
324
325
|
auth,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpManagerResource.js","names":["useState","useEffect","useMemo","useEffectEvent","useRef","useResource","resource","withKey","useClientLookup","useAssistantClientRef","attachTransformScopes","ClientOutput","useAssistantScopeEffect","ModelContext","Tool","McpServerResource","McpLocalStorage","MCPStorage","MCPStorageElement","assertUniqueServerIds","MCPAuthConfig","MCPConnector","MCPCustomServerRecord","MCPManagerState","McpManagerResourceProps","connectors","storage","oauthRedirectUri","autoConnect","connectionTimeout","defaultRedirectUri","window","location","origin","NO_CONNECTORS","reportCustomStorageFailure","operation","error","console","persistCustomServers","records","saveCustomServers","useMcpManagerResource","props","$","_c","t0","redirectUri","t1","storageElement","t2","Symbol","for","customServers","setCustomServers","isHydrated","setIsHydrated","hydratedRef","storageRef","t3","Promise","resolve","persistenceQueueRef","t4","t5","current","t6","signal","markHydrated","cancelled","loadCustomServers","t7","prev","length","persistedIds","Set","map","_temp","filter","r_0","has","r","id","hydrate","signal_0","t8","t10","t9","targetStorage","then","_temp2","_temp3","t11","t12","c_0","c","kind","name","url","icon","auth","cache","undefined","elicitation","onRemove","_temp4","connectorElements","t13","s_0","s","prev_0","x","customElements","serverElements","lookup","all","state","_temp5","_temp6","t14","servers","out","server","connectionState","tool","tools","fullName","type","description","parameters","inputSchema","execute","args","get","key","callTool","toolkit","clientRef","t15","client","modelContext","register","getModelContext","t16","t17","index","list","entry","Error","serverByKind","t18","t19","query","t20","t21","t22","index_0","t23","index_1","connectionTimeout_0","record","crypto","randomUUID","Date","now","Math","random","toString","slice","createdAt","prev_1","some","c_1","remove","clearAuthState","prev_2","s_3","t24","getState","connector","customServer","addCustomServer","removeServer","McpManagerResource","scopes","parent","source","s_1","s_2"],"sources":["../../src/resources/McpManagerResource.ts"],"sourcesContent":["import { useState, useEffect, useMemo, useEffectEvent, useRef } from \"react\";\nimport { useResource, resource, withKey } from \"@assistant-ui/tap\";\nimport {\n useClientLookup,\n useAssistantClientRef,\n attachTransformScopes,\n type ClientOutput,\n} from \"@assistant-ui/store\";\nimport { useAssistantScopeEffect } from \"@assistant-ui/store/client\";\nimport { ModelContext } from \"@assistant-ui/core/store\";\nimport type { Tool } from \"assistant-stream\";\nimport { McpServerResource } from \"./McpServerResource\";\nimport { McpLocalStorage } from \"./storage/McpLocalStorage\";\nimport type { MCPStorage, MCPStorageElement } from \"./storage/types\";\nimport { assertUniqueServerIds } from \"../utils/serverId\";\nimport type {\n MCPAuthConfig,\n MCPConnector,\n MCPCustomServerRecord,\n MCPManagerState,\n} from \"../mcp-scope\";\n\nexport type McpManagerResourceProps = {\n connectors?: MCPConnector[] | undefined;\n storage?: MCPStorageElement | undefined;\n /** OAuth redirect target. Defaults to `${origin}/mcp/callback`. */\n oauthRedirectUri?: string | undefined;\n /** Connect on mount when usable auth exists. Default true. */\n autoConnect?: boolean | undefined;\n /** Optional timeout in milliseconds for connect/listTools calls. Disabled by default. */\n connectionTimeout?: number | undefined;\n};\n\nfunction defaultRedirectUri(): string {\n if (typeof window === \"undefined\") return \"\";\n return `${window.location.origin}/mcp/callback`;\n}\n\n// Stable empty fallback so an absent `connectors` prop doesn't produce a fresh\n// array each render (which would invalidate the serverElements memo below).\nconst NO_CONNECTORS: MCPConnector[] = [];\n\nconst reportCustomStorageFailure = (\n operation: \"load\" | \"save\",\n error: unknown,\n) => {\n console.error(\n `[assistant-ui/react-mcp] failed to ${operation} custom servers:`,\n error,\n );\n};\n\nconst persistCustomServers = async (\n storage: MCPStorage,\n records: MCPCustomServerRecord[],\n) => {\n try {\n await storage.saveCustomServers(records);\n } catch (error) {\n reportCustomStorageFailure(\"save\", error);\n }\n};\n\nconst useMcpManagerResource = (\n props: McpManagerResourceProps,\n): ClientOutput<\"mcp\"> => {\n const connectors = props.connectors ?? NO_CONNECTORS;\n const autoConnect = props.autoConnect ?? true;\n const redirectUri = props.oauthRedirectUri ?? defaultRedirectUri();\n const connectionTimeout = props.connectionTimeout;\n\n const storageElement = props.storage ?? McpLocalStorage();\n const storage = useResource(storageElement);\n\n const [customServers, setCustomServers] = useState<MCPCustomServerRecord[]>(\n [],\n );\n const [isHydrated, setIsHydrated] = useState(false);\n\n const hydratedRef = useRef(false);\n const storageRef = useRef(storage);\n const persistenceQueueRef = useRef(Promise.resolve());\n\n useEffect(() => {\n storageRef.current = storage;\n }, [storage]);\n\n const hydrate = useEffectEvent(async (signal: { cancelled: boolean }) => {\n const markHydrated = () => {\n if (!signal.cancelled) {\n hydratedRef.current = true;\n setIsHydrated(true);\n }\n };\n\n let records: Awaited<ReturnType<typeof storage.loadCustomServers>>;\n try {\n records = await storage.loadCustomServers();\n } catch (error) {\n if (!signal.cancelled) {\n reportCustomStorageFailure(\"load\", error);\n }\n markHydrated();\n return;\n }\n\n if (signal.cancelled) return;\n // Merge rather than replace so any addCustomServer calls that\n // happened before hydration resolved aren't silently overwritten.\n // Persisted order wins; pre-hydration locals append.\n setCustomServers((prev) => {\n if (prev.length === 0) return records;\n const persistedIds = new Set(records.map((r) => r.id));\n return [...records, ...prev.filter((r) => !persistedIds.has(r.id))];\n });\n markHydrated();\n });\n\n useEffect(() => {\n const signal = { cancelled: false };\n void hydrate(signal);\n return () => {\n signal.cancelled = true;\n };\n }, []);\n\n useEffect(() => {\n if (!hydratedRef.current) return;\n const targetStorage = storageRef.current;\n persistenceQueueRef.current = persistenceQueueRef.current.then(() =>\n persistCustomServers(targetStorage, customServers),\n );\n }, [customServers]);\n\n const serverElements = useMemo(() => {\n assertUniqueServerIds([\n ...connectors.map((c) => c.id),\n ...customServers.map((s) => s.id),\n ]);\n\n const connectorElements = connectors.map((c) =>\n withKey(\n c.id,\n McpServerResource({\n id: c.id,\n kind: \"connector\",\n name: c.name,\n url: c.url,\n icon: c.icon,\n auth: c.auth,\n storage,\n redirectUri,\n autoConnect,\n connectionTimeout: c.connectionTimeout ?? connectionTimeout,\n ...(c.cache !== undefined ? { cache: c.cache } : {}),\n ...(c.elicitation !== undefined\n ? { elicitation: c.elicitation }\n : {}),\n onRemove: async () => {\n // connectors cannot be removed\n },\n }),\n ),\n );\n const customElements = customServers.map((s) =>\n withKey(\n s.id,\n McpServerResource({\n id: s.id,\n kind: \"custom\",\n name: s.name,\n url: s.url,\n auth: s.auth,\n storage,\n redirectUri,\n autoConnect,\n connectionTimeout: s.connectionTimeout ?? connectionTimeout,\n ...(s.cache !== undefined ? { cache: s.cache } : {}),\n ...(s.elicitation !== undefined\n ? { elicitation: s.elicitation }\n : {}),\n onRemove: async () => {\n setCustomServers((prev) => prev.filter((x) => x.id !== s.id));\n },\n }),\n ),\n );\n return [...connectorElements, ...customElements];\n }, [\n connectors,\n customServers,\n storage,\n redirectUri,\n autoConnect,\n connectionTimeout,\n ]);\n\n const lookup = useClientLookup(serverElements);\n\n const state = useMemo<MCPManagerState>(() => {\n const all = lookup.state;\n return {\n servers: all,\n connectors: all.filter((s) => s.kind === \"connector\"),\n customServers: all.filter((s) => s.kind === \"custom\"),\n isHydrated,\n };\n }, [lookup.state, isHydrated]);\n\n // ─── Auto-register MCP tools as frontend tools in modelContext ─────\n // Build the toolkit from connected servers; re-register when the visible\n // tool surface changes. Tool names are prefixed with the server id to\n // avoid collisions across connected servers — connector ids must not\n // contain `__` (enforced by `defineConnector`); `addCustomServer`\n // generates UUIDs that satisfy the constraint by construction.\n const toolkit = useMemo<Record<string, Tool<any, any>>>(() => {\n const out: Record<string, Tool<any, any>> = {};\n for (const server of state.servers) {\n if (server.connectionState !== \"connected\") continue;\n for (const tool of server.tools) {\n const fullName = `${server.id}__${tool.name}`;\n out[fullName] = {\n type: \"frontend\",\n ...(tool.description !== undefined\n ? { description: tool.description }\n : {}),\n parameters: tool.inputSchema as never,\n execute: (args) =>\n lookup.get({ key: server.id }).callTool(tool.name, args as unknown),\n };\n }\n }\n return out;\n }, [state, lookup]);\n\n const clientRef = useAssistantClientRef();\n\n useAssistantScopeEffect(\n \"modelContext\",\n () => {\n const client = clientRef.current;\n if (!client) return;\n return client.modelContext.register({\n getModelContext: () => ({ tools: toolkit }),\n });\n },\n [toolkit],\n );\n\n const serverByKind = (kind: \"connector\" | \"custom\", index: number) => {\n const list = kind === \"connector\" ? state.connectors : state.customServers;\n const entry = list[index];\n if (!entry) {\n throw new Error(\n `McpManagerResource: no ${kind} at index ${index} (length ${list.length})`,\n );\n }\n return lookup.get({ key: entry.id });\n };\n\n return {\n getState: () => state,\n server: (query) => {\n if (\"id\" in query) return lookup.get({ key: query.id });\n return serverByKind(query.kind, query.index);\n },\n connector: ({ index }) => serverByKind(\"connector\", index),\n customServer: ({ index }) => serverByKind(\"custom\", index),\n addCustomServer: async ({\n name,\n url,\n auth,\n connectionTimeout,\n cache,\n elicitation,\n }) => {\n const record: MCPCustomServerRecord = {\n id:\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : `mcp-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n name,\n url,\n auth: auth as MCPAuthConfig,\n connectionTimeout,\n ...(cache !== undefined ? { cache } : {}),\n ...(elicitation !== undefined ? { elicitation } : {}),\n createdAt: Date.now(),\n };\n setCustomServers((prev) => [...prev, record]);\n return record.id;\n },\n removeServer: async (id) => {\n // removeServer is custom-server only — connectors are app-defined\n // and not user-removable. Refuse rather than silently no-op.\n if (state.connectors.some((c) => c.id === id)) {\n throw new Error(\n `Cannot remove connector \"${id}\" — connectors are app-defined and not removable. Use a custom server id instead.`,\n );\n }\n // Delegate to McpServerResource.remove() which disconnects,\n // clears auth state, and unregisters from customServers in one\n // place. Fallback to manual cleanup if the lookup is empty\n // (server already gone).\n try {\n await lookup.get({ key: id }).remove();\n } catch {\n await storage.clearAuthState(id);\n setCustomServers((prev) => prev.filter((s) => s.id !== id));\n }\n },\n };\n};\n\nexport const McpManagerResource = resource(useMcpManagerResource);\n\n// Ensure modelContext exists as a sibling when the manager mounts. If an\n// ancestor (e.g. a chat runtime) already provides modelContext, this is a\n// no-op; otherwise it's auto-mounted alongside `mcp`.\nattachTransformScopes(useMcpManagerResource, (scopes, parent) => {\n if (!scopes.modelContext && parent.modelContext.source === null) {\n scopes.modelContext = ModelContext();\n }\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"McpManagerResource.js","names":["useState","useEffect","useMemo","useEffectEvent","useRef","useResource","resource","withKey","useClientLookup","useAssistantClientRef","attachTransformScopes","ClientOutput","useAssistantScopeEffect","ModelContext","createMcpId","Tool","McpServerResource","McpLocalStorage","MCPStorage","MCPStorageElement","assertUniqueServerIds","MCPAuthConfig","MCPConnector","MCPCustomServerRecord","MCPManagerState","McpManagerResourceProps","connectors","storage","oauthRedirectUri","autoConnect","connectionTimeout","defaultRedirectUri","window","location","origin","NO_CONNECTORS","reportCustomStorageFailure","operation","error","console","persistCustomServers","records","saveCustomServers","useMcpManagerResource","props","$","_c","t0","redirectUri","t1","storageElement","t2","Symbol","for","customServers","setCustomServers","isHydrated","setIsHydrated","hydratedRef","storageRef","t3","Promise","resolve","persistenceQueueRef","t4","t5","current","t6","signal","markHydrated","cancelled","loadCustomServers","t7","prev","length","persistedIds","Set","map","_temp","filter","r_0","has","r","id","hydrate","signal_0","t8","t10","t9","targetStorage","then","_temp2","_temp3","t11","t12","c_0","c","kind","name","url","icon","auth","cache","undefined","elicitation","onRemove","_temp4","connectorElements","t13","s_0","s","prev_0","x","customElements","serverElements","lookup","all","state","_temp5","_temp6","t14","servers","out","server","connectionState","tool","tools","fullName","type","description","parameters","inputSchema","execute","args","get","key","callTool","toolkit","clientRef","t15","client","modelContext","register","getModelContext","t16","t17","index","list","entry","Error","serverByKind","t18","t19","query","t20","t21","t22","index_0","t23","index_1","connectionTimeout_0","record","createdAt","Date","now","prev_1","some","c_1","remove","clearAuthState","prev_2","s_3","t24","getState","connector","customServer","addCustomServer","removeServer","McpManagerResource","scopes","parent","source","s_1","s_2"],"sources":["../../src/resources/McpManagerResource.ts"],"sourcesContent":["import { useState, useEffect, useMemo, useEffectEvent, useRef } from \"react\";\nimport { useResource, resource, withKey } from \"@assistant-ui/tap\";\nimport {\n useClientLookup,\n useAssistantClientRef,\n attachTransformScopes,\n type ClientOutput,\n} from \"@assistant-ui/store\";\nimport { useAssistantScopeEffect } from \"@assistant-ui/store/client\";\nimport { ModelContext } from \"@assistant-ui/core/store\";\nimport { createMcpId } from \"../utils/createMcpId\";\nimport type { Tool } from \"assistant-stream\";\nimport { McpServerResource } from \"./McpServerResource\";\nimport { McpLocalStorage } from \"./storage/McpLocalStorage\";\nimport type { MCPStorage, MCPStorageElement } from \"./storage/types\";\nimport { assertUniqueServerIds } from \"../utils/serverId\";\nimport type {\n MCPAuthConfig,\n MCPConnector,\n MCPCustomServerRecord,\n MCPManagerState,\n} from \"../mcp-scope\";\n\nexport type McpManagerResourceProps = {\n connectors?: MCPConnector[] | undefined;\n storage?: MCPStorageElement | undefined;\n /** OAuth redirect target. Defaults to `${origin}/mcp/callback`. */\n oauthRedirectUri?: string | undefined;\n /** Connect on mount when usable auth exists. Default true. */\n autoConnect?: boolean | undefined;\n /** Optional timeout in milliseconds for connect/listTools calls. Disabled by default. */\n connectionTimeout?: number | undefined;\n};\n\nfunction defaultRedirectUri(): string {\n if (typeof window === \"undefined\") return \"\";\n return `${window.location.origin}/mcp/callback`;\n}\n\n// Stable empty fallback so an absent `connectors` prop doesn't produce a fresh\n// array each render (which would invalidate the serverElements memo below).\nconst NO_CONNECTORS: MCPConnector[] = [];\n\nconst reportCustomStorageFailure = (\n operation: \"load\" | \"save\",\n error: unknown,\n) => {\n console.error(\n `[assistant-ui/react-mcp] failed to ${operation} custom servers:`,\n error,\n );\n};\n\nconst persistCustomServers = async (\n storage: MCPStorage,\n records: MCPCustomServerRecord[],\n) => {\n try {\n await storage.saveCustomServers(records);\n } catch (error) {\n reportCustomStorageFailure(\"save\", error);\n }\n};\n\nconst useMcpManagerResource = (\n props: McpManagerResourceProps,\n): ClientOutput<\"mcp\"> => {\n const connectors = props.connectors ?? NO_CONNECTORS;\n const autoConnect = props.autoConnect ?? true;\n const redirectUri = props.oauthRedirectUri ?? defaultRedirectUri();\n const connectionTimeout = props.connectionTimeout;\n\n const storageElement = props.storage ?? McpLocalStorage();\n const storage = useResource(storageElement);\n\n const [customServers, setCustomServers] = useState<MCPCustomServerRecord[]>(\n [],\n );\n const [isHydrated, setIsHydrated] = useState(false);\n\n const hydratedRef = useRef(false);\n const storageRef = useRef(storage);\n const persistenceQueueRef = useRef(Promise.resolve());\n\n useEffect(() => {\n storageRef.current = storage;\n }, [storage]);\n\n const hydrate = useEffectEvent(async (signal: { cancelled: boolean }) => {\n const markHydrated = () => {\n if (!signal.cancelled) {\n hydratedRef.current = true;\n setIsHydrated(true);\n }\n };\n\n let records: Awaited<ReturnType<typeof storage.loadCustomServers>>;\n try {\n records = await storage.loadCustomServers();\n } catch (error) {\n if (!signal.cancelled) {\n reportCustomStorageFailure(\"load\", error);\n }\n markHydrated();\n return;\n }\n\n if (signal.cancelled) return;\n // Merge rather than replace so any addCustomServer calls that\n // happened before hydration resolved aren't silently overwritten.\n // Persisted order wins; pre-hydration locals append.\n setCustomServers((prev) => {\n if (prev.length === 0) return records;\n const persistedIds = new Set(records.map((r) => r.id));\n return [...records, ...prev.filter((r) => !persistedIds.has(r.id))];\n });\n markHydrated();\n });\n\n useEffect(() => {\n const signal = { cancelled: false };\n void hydrate(signal);\n return () => {\n signal.cancelled = true;\n };\n }, []);\n\n useEffect(() => {\n if (!hydratedRef.current) return;\n const targetStorage = storageRef.current;\n persistenceQueueRef.current = persistenceQueueRef.current.then(() =>\n persistCustomServers(targetStorage, customServers),\n );\n }, [customServers]);\n\n const serverElements = useMemo(() => {\n assertUniqueServerIds([\n ...connectors.map((c) => c.id),\n ...customServers.map((s) => s.id),\n ]);\n\n const connectorElements = connectors.map((c) =>\n withKey(\n c.id,\n McpServerResource({\n id: c.id,\n kind: \"connector\",\n name: c.name,\n url: c.url,\n icon: c.icon,\n auth: c.auth,\n storage,\n redirectUri,\n autoConnect,\n connectionTimeout: c.connectionTimeout ?? connectionTimeout,\n ...(c.cache !== undefined ? { cache: c.cache } : {}),\n ...(c.elicitation !== undefined\n ? { elicitation: c.elicitation }\n : {}),\n onRemove: async () => {\n // connectors cannot be removed\n },\n }),\n ),\n );\n const customElements = customServers.map((s) =>\n withKey(\n s.id,\n McpServerResource({\n id: s.id,\n kind: \"custom\",\n name: s.name,\n url: s.url,\n auth: s.auth,\n storage,\n redirectUri,\n autoConnect,\n connectionTimeout: s.connectionTimeout ?? connectionTimeout,\n ...(s.cache !== undefined ? { cache: s.cache } : {}),\n ...(s.elicitation !== undefined\n ? { elicitation: s.elicitation }\n : {}),\n onRemove: async () => {\n setCustomServers((prev) => prev.filter((x) => x.id !== s.id));\n },\n }),\n ),\n );\n return [...connectorElements, ...customElements];\n }, [\n connectors,\n customServers,\n storage,\n redirectUri,\n autoConnect,\n connectionTimeout,\n ]);\n\n const lookup = useClientLookup(serverElements);\n\n const state = useMemo<MCPManagerState>(() => {\n const all = lookup.state;\n return {\n servers: all,\n connectors: all.filter((s) => s.kind === \"connector\"),\n customServers: all.filter((s) => s.kind === \"custom\"),\n isHydrated,\n };\n }, [lookup.state, isHydrated]);\n\n // ─── Auto-register MCP tools as frontend tools in modelContext ─────\n // Build the toolkit from connected servers; re-register when the visible\n // tool surface changes. Tool names are prefixed with the server id to\n // avoid collisions across connected servers — connector ids must not\n // contain `__` (enforced by `defineConnector`); `addCustomServer`\n // generates UUIDs that satisfy the constraint by construction.\n const toolkit = useMemo<Record<string, Tool<any, any>>>(() => {\n const out: Record<string, Tool<any, any>> = {};\n for (const server of state.servers) {\n if (server.connectionState !== \"connected\") continue;\n for (const tool of server.tools) {\n const fullName = `${server.id}__${tool.name}`;\n out[fullName] = {\n type: \"frontend\",\n ...(tool.description !== undefined\n ? { description: tool.description }\n : {}),\n parameters: tool.inputSchema as never,\n execute: (args) =>\n lookup.get({ key: server.id }).callTool(tool.name, args as unknown),\n };\n }\n }\n return out;\n }, [state, lookup]);\n\n const clientRef = useAssistantClientRef();\n\n useAssistantScopeEffect(\n \"modelContext\",\n () => {\n const client = clientRef.current;\n if (!client) return;\n return client.modelContext.register({\n getModelContext: () => ({ tools: toolkit }),\n });\n },\n [toolkit],\n );\n\n const serverByKind = (kind: \"connector\" | \"custom\", index: number) => {\n const list = kind === \"connector\" ? state.connectors : state.customServers;\n const entry = list[index];\n if (!entry) {\n throw new Error(\n `McpManagerResource: no ${kind} at index ${index} (length ${list.length})`,\n );\n }\n return lookup.get({ key: entry.id });\n };\n\n return {\n getState: () => state,\n server: (query) => {\n if (\"id\" in query) return lookup.get({ key: query.id });\n return serverByKind(query.kind, query.index);\n },\n connector: ({ index }) => serverByKind(\"connector\", index),\n customServer: ({ index }) => serverByKind(\"custom\", index),\n addCustomServer: async ({\n name,\n url,\n auth,\n connectionTimeout,\n cache,\n elicitation,\n }) => {\n const record: MCPCustomServerRecord = {\n id: createMcpId(),\n name,\n url,\n auth: auth as MCPAuthConfig,\n connectionTimeout,\n ...(cache !== undefined ? { cache } : {}),\n ...(elicitation !== undefined ? { elicitation } : {}),\n createdAt: Date.now(),\n };\n setCustomServers((prev) => [...prev, record]);\n return record.id;\n },\n removeServer: async (id) => {\n // removeServer is custom-server only — connectors are app-defined\n // and not user-removable. Refuse rather than silently no-op.\n if (state.connectors.some((c) => c.id === id)) {\n throw new Error(\n `Cannot remove connector \"${id}\" — connectors are app-defined and not removable. Use a custom server id instead.`,\n );\n }\n // Delegate to McpServerResource.remove() which disconnects,\n // clears auth state, and unregisters from customServers in one\n // place. Fallback to manual cleanup if the lookup is empty\n // (server already gone).\n try {\n await lookup.get({ key: id }).remove();\n } catch {\n await storage.clearAuthState(id);\n setCustomServers((prev) => prev.filter((s) => s.id !== id));\n }\n },\n };\n};\n\nexport const McpManagerResource = resource(useMcpManagerResource);\n\n// Ensure modelContext exists as a sibling when the manager mounts. If an\n// ancestor (e.g. a chat runtime) already provides modelContext, this is a\n// no-op; otherwise it's auto-mounted alongside `mcp`.\nattachTransformScopes(useMcpManagerResource, (scopes, parent) => {\n if (!scopes.modelContext && parent.modelContext.source === null) {\n scopes.modelContext = ModelContext();\n }\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpServerResource.d.ts","names":[],"sources":["../../src/resources/McpServerResource.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"McpServerResource.d.ts","names":[],"sources":["../../src/resources/McpServerResource.ts"],"mappings":";;;;KA4BY;EACV;EACA,MAAM;EACN;EACA;EACA;EACA,MAAM;EACN,SAAS;EACT;EACA;EACA;EACA;aAAmB;;WACV;EACT,gBAAgB;;cAwoBL,+CAAiB,SAAA,4BAAA,OAAA"}
|
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
import { assertValidServerId } from "../utils/serverId.js";
|
|
2
|
+
import { createMcpId } from "../utils/createMcpId.js";
|
|
2
3
|
import { createOAuthProvider } from "../auth/createOAuthProvider.js";
|
|
3
4
|
import { buildHeaders } from "../auth/buildHeaders.js";
|
|
4
5
|
import { validateElicitationContent } from "./validateElicitationContent.js";
|
|
5
6
|
import { useEffect, useEffectEvent, useMemo, useRef, useState } from "@assistant-ui/tap/react-shim";
|
|
6
|
-
import { resource } from "@assistant-ui/tap";
|
|
7
|
+
import { resource, useResource, withKey } from "@assistant-ui/tap";
|
|
7
8
|
import { Client, StreamableHTTPClientTransport, UnauthorizedError } from "@modelcontextprotocol/client";
|
|
8
9
|
//#region src/resources/McpServerResource.ts
|
|
9
|
-
const
|
|
10
|
+
const getConnectionDependencies = (props) => {
|
|
11
|
+
const auth = props.auth;
|
|
12
|
+
const authDependencies = auth.type === "bearer" ? [auth.type, auth.token] : auth.type === "oauth" ? [
|
|
13
|
+
auth.type,
|
|
14
|
+
auth.scopes?.length,
|
|
15
|
+
...auth.scopes ?? [],
|
|
16
|
+
auth.authorizationEndpoint,
|
|
17
|
+
auth.tokenEndpoint,
|
|
18
|
+
auth.registrationEndpoint,
|
|
19
|
+
auth.clientId,
|
|
20
|
+
auth.clientSecret
|
|
21
|
+
] : [auth.type];
|
|
22
|
+
return [
|
|
23
|
+
props.id,
|
|
24
|
+
props.url,
|
|
25
|
+
...authDependencies,
|
|
26
|
+
props.redirectUri,
|
|
27
|
+
props.cache?.defaultTtlMs,
|
|
28
|
+
props.elicitation !== false
|
|
29
|
+
];
|
|
30
|
+
};
|
|
31
|
+
const areConnectionDependenciesEqual = (left, right) => left.length === right.length && left.every((value, index) => Object.is(value, right[index]));
|
|
32
|
+
const useMcpServerResourceInstance = (props) => {
|
|
10
33
|
assertValidServerId(props.id);
|
|
11
34
|
const [connectionState, setConnectionState] = useState("disconnected");
|
|
12
35
|
const [tools, setTools] = useState([]);
|
|
@@ -16,7 +39,6 @@ const useMcpServerResource = (props) => {
|
|
|
16
39
|
const clientRef = useRef(null);
|
|
17
40
|
const transportRef = useRef(null);
|
|
18
41
|
const pendingTransportRef = useRef(null);
|
|
19
|
-
const transportCloseQueueRef = useRef(Promise.resolve());
|
|
20
42
|
const connectionGenerationRef = useRef(0);
|
|
21
43
|
const elicitationResolversRef = useRef(/* @__PURE__ */ new Map());
|
|
22
44
|
const pendingDisposalRef = useRef(null);
|
|
@@ -27,10 +49,10 @@ const useMcpServerResource = (props) => {
|
|
|
27
49
|
} catch {}
|
|
28
50
|
};
|
|
29
51
|
const closeQueuedTransports = (transports) => {
|
|
30
|
-
const task = transportCloseQueueRef.current.then(async () => {
|
|
52
|
+
const task = props.transportCloseQueueRef.current.then(async () => {
|
|
31
53
|
await Promise.all(transports.map(closeTransportSafely));
|
|
32
54
|
});
|
|
33
|
-
transportCloseQueueRef.current = task;
|
|
55
|
+
props.transportCloseQueueRef.current = task;
|
|
34
56
|
return task;
|
|
35
57
|
};
|
|
36
58
|
const closePendingTransport = async () => {
|
|
@@ -58,15 +80,17 @@ const useMcpServerResource = (props) => {
|
|
|
58
80
|
const cancelPendingElicitations = () => {
|
|
59
81
|
for (const [id_1] of elicitationResolversRef.current) resolvePendingElicitation(id_1, { action: "cancel" });
|
|
60
82
|
};
|
|
61
|
-
const
|
|
83
|
+
const detachTransports = () => {
|
|
62
84
|
cancelPendingElicitations();
|
|
63
85
|
const pendingTransport = pendingTransportRef.current;
|
|
64
86
|
const activeTransport = transportRef.current;
|
|
65
87
|
pendingTransportRef.current = null;
|
|
66
88
|
transportRef.current = null;
|
|
67
89
|
clientRef.current = null;
|
|
68
|
-
|
|
69
|
-
|
|
90
|
+
return [...new Set([pendingTransport, activeTransport].filter((transport_1) => transport_1 !== null))];
|
|
91
|
+
};
|
|
92
|
+
const closeTransports = async () => {
|
|
93
|
+
await closeQueuedTransports(detachTransports());
|
|
70
94
|
};
|
|
71
95
|
const isCurrentConnection = (generation) => mountedRef.current && generation === connectionGenerationRef.current;
|
|
72
96
|
const createInterruptedAuthError = (cause) => new Error(`MCP server "${props.id}" authorization was interrupted before completion.`, cause === void 0 ? void 0 : { cause });
|
|
@@ -149,7 +173,7 @@ const useMcpServerResource = (props) => {
|
|
|
149
173
|
if (!isCurrentConnection(generation_1)) return Promise.resolve({ action: "cancel" });
|
|
150
174
|
if (!("requestedSchema" in request.params)) return Promise.resolve({ action: "cancel" });
|
|
151
175
|
const { message, requestedSchema } = request.params;
|
|
152
|
-
const id_2 =
|
|
176
|
+
const id_2 = createMcpId();
|
|
153
177
|
const promise_0 = new Promise((resolve) => {
|
|
154
178
|
const onAbort = () => {
|
|
155
179
|
resolvePendingElicitation(id_2, { action: "cancel" });
|
|
@@ -286,6 +310,7 @@ const useMcpServerResource = (props) => {
|
|
|
286
310
|
doConnect();
|
|
287
311
|
});
|
|
288
312
|
useEffect(() => {
|
|
313
|
+
const transportCloseQueueRef = props.transportCloseQueueRef;
|
|
289
314
|
const previousDisposal = pendingDisposalRef.current;
|
|
290
315
|
if (previousDisposal) previousDisposal.cancelled = true;
|
|
291
316
|
const pendingDisposal = { cancelled: false };
|
|
@@ -296,13 +321,15 @@ const useMcpServerResource = (props) => {
|
|
|
296
321
|
return () => {
|
|
297
322
|
mountedRef.current = false;
|
|
298
323
|
signal_0.cancelled = true;
|
|
299
|
-
|
|
324
|
+
const task_0 = transportCloseQueueRef.current.then(async () => {
|
|
325
|
+
await new Promise((resolve_0) => queueMicrotask(resolve_0));
|
|
300
326
|
if (pendingDisposal.cancelled) return;
|
|
301
327
|
connectionGenerationRef.current += 1;
|
|
302
|
-
|
|
328
|
+
await Promise.all(detachTransports().map(closeTransportSafely));
|
|
303
329
|
});
|
|
330
|
+
transportCloseQueueRef.current = task_0;
|
|
304
331
|
};
|
|
305
|
-
}, []);
|
|
332
|
+
}, [props.transportCloseQueueRef]);
|
|
306
333
|
const state = useMemo(() => ({
|
|
307
334
|
id: props.id,
|
|
308
335
|
kind: props.kind,
|
|
@@ -394,7 +421,27 @@ const useMcpServerResource = (props) => {
|
|
|
394
421
|
}
|
|
395
422
|
};
|
|
396
423
|
};
|
|
397
|
-
const
|
|
424
|
+
const McpServerResourceInstance = resource(useMcpServerResourceInstance);
|
|
425
|
+
const McpServerResource = resource(function useMcpServerResource(props) {
|
|
426
|
+
const transportCloseQueueRef = useRef(Promise.resolve());
|
|
427
|
+
const dependencies = getConnectionDependencies(props);
|
|
428
|
+
const [connection, setConnection] = useState({
|
|
429
|
+
dependencies,
|
|
430
|
+
generation: 0
|
|
431
|
+
});
|
|
432
|
+
let currentConnection = connection;
|
|
433
|
+
if (!areConnectionDependenciesEqual(connection.dependencies, dependencies)) {
|
|
434
|
+
currentConnection = {
|
|
435
|
+
dependencies,
|
|
436
|
+
generation: connection.generation + 1
|
|
437
|
+
};
|
|
438
|
+
setConnection(currentConnection);
|
|
439
|
+
}
|
|
440
|
+
return useResource(withKey(currentConnection.generation, McpServerResourceInstance({
|
|
441
|
+
...props,
|
|
442
|
+
transportCloseQueueRef
|
|
443
|
+
})));
|
|
444
|
+
});
|
|
398
445
|
//#endregion
|
|
399
446
|
export { McpServerResource };
|
|
400
447
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpServerResource.js","names":["useState","useRef","useEffect","useMemo","useEffectEvent","resource","ClientOutput","Client","StreamableHTTPClientTransport","UnauthorizedError","ClientOptions","ElicitRequest","ElicitResult","StreamableHTTPClientTransportOptions","createOAuthProvider","buildHeaders","assertValidServerId","validateElicitationContent","MCPStorage","MCPAuthConfig","MCPConnectionState","MCPElicitation","MCPElicitationResponse","MCPServerKind","MCPServerState","MCPToolInfo","McpServerResourceProps","id","kind","name","url","icon","auth","storage","redirectUri","autoConnect","connectionTimeout","cache","defaultTtlMs","elicitation","onRemove","Promise","useMcpServerResource","props","connectionState","setConnectionState","tools","setTools","lastError","setLastError","message","authorizationUrl","setAuthorizationUrl","pendingElicitations","setPendingElicitations","clientRef","transportRef","pendingTransportRef","transportCloseQueueRef","resolve","connectionGenerationRef","elicitationResolversRef","Map","result","signal","AbortSignal","onAbort","requestedSchema","pendingDisposalRef","cancelled","mountedRef","closeTransportSafely","transport","close","closeQueuedTransports","transports","task","current","then","all","map","closePendingTransport","resolvePendingElicitation","entry","get","delete","removeEventListener","filter","setPendingElicitationError","error","NonNullable","has","cancelPendingElicitations","action","closeTransports","pendingTransport","activeTransport","Set","isCurrentConnection","generation","createInterruptedAuthError","cause","Error","undefined","withConnectionTimeout","promise","T","phase","startedAt","timeoutMs","remainingMs","Date","now","timeoutError","timeout","ReturnType","setTimeout","race","_","reject","clearTimeout","buildTransport","type","authProvider","serverId","config","onAuthorizationUrl","toString","URL","persisted","loadAuthState","headers","transportOpts","requestInit","applyToolsList","list","Array","description","inputSchema","t","info","syncTools","client","options","listPromise","listTools","finalizeConnect","clientOptions","capabilities","listChanged","autoRefresh","debounceMs","onChanged","items","defaultCacheTtlMs","version","setRequestHandler","request","context","params","crypto","randomUUID","Math","random","slice","set","aborted","addEventListener","once","connect","synced","doConnect","err","String","doDisconnect","doCompleteAuth","callbackUrl","code","searchParams","finishAuth","connected","tryAutoConnect","token","Awaited","tokens","previousDisposal","pendingDisposal","queueMicrotask","state","getState","disconnect","remove","clearAuthState","callTool","args","arguments","Record","listResources","readResource","uri","completeAuth","answerElicitation","response","property","content","isArray","errors","properties","length","join","McpServerResource"],"sources":["../../src/resources/McpServerResource.ts"],"sourcesContent":["import { useState, useRef, useEffect, useMemo, useEffectEvent } from \"react\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type { ClientOutput } from \"@assistant-ui/store\";\nimport {\n Client,\n StreamableHTTPClientTransport,\n UnauthorizedError,\n type ClientOptions,\n type ElicitRequest,\n type ElicitResult,\n type StreamableHTTPClientTransportOptions,\n} from \"@modelcontextprotocol/client\";\nimport { createOAuthProvider } from \"../auth/createOAuthProvider\";\nimport { buildHeaders } from \"../auth/buildHeaders\";\nimport { assertValidServerId } from \"../utils/serverId\";\nimport { validateElicitationContent } from \"./validateElicitationContent\";\nimport type { MCPStorage } from \"./storage/types\";\nimport type {\n MCPAuthConfig,\n MCPConnectionState,\n MCPElicitation,\n MCPElicitationResponse,\n MCPServerKind,\n MCPServerState,\n MCPToolInfo,\n} from \"../mcp-scope\";\n\nexport type McpServerResourceProps = {\n id: string;\n kind: MCPServerKind;\n name: string;\n url: string;\n icon?: string | undefined;\n auth: MCPAuthConfig;\n storage: MCPStorage;\n redirectUri: string;\n autoConnect: boolean;\n connectionTimeout?: number | undefined;\n cache?: { readonly defaultTtlMs?: number } | undefined;\n readonly elicitation?: boolean;\n onRemove: () => Promise<void>;\n};\n\nconst useMcpServerResource = (\n props: McpServerResourceProps,\n): ClientOutput<\"mcpServer\"> => {\n assertValidServerId(props.id);\n const [connectionState, setConnectionState] =\n useState<MCPConnectionState>(\"disconnected\");\n const [tools, setTools] = useState<MCPToolInfo[]>([]);\n const [lastError, setLastError] = useState<{ message: string } | null>(null);\n const [authorizationUrl, setAuthorizationUrl] = useState<string | null>(null);\n const [pendingElicitations, setPendingElicitations] = useState<\n MCPElicitation[]\n >([]);\n\n const clientRef = useRef<Client | null>(null);\n const transportRef = useRef<StreamableHTTPClientTransport | null>(null);\n const pendingTransportRef = useRef<StreamableHTTPClientTransport | null>(\n null,\n );\n const transportCloseQueueRef = useRef(Promise.resolve());\n const connectionGenerationRef = useRef(0);\n const elicitationResolversRef = useRef(\n new Map<\n string,\n {\n resolve: (result: ElicitResult) => void;\n signal: AbortSignal;\n onAbort: () => void;\n requestedSchema: unknown;\n }\n >(),\n );\n const pendingDisposalRef = useRef<{ cancelled: boolean } | null>(null);\n const mountedRef = useRef(true);\n\n const closeTransportSafely = async (\n transport: StreamableHTTPClientTransport,\n ): Promise<void> => {\n try {\n await transport.close();\n } catch {\n // ignore close errors\n }\n };\n\n const closeQueuedTransports = (\n transports: StreamableHTTPClientTransport[],\n ): Promise<void> => {\n const task = transportCloseQueueRef.current.then(async () => {\n await Promise.all(transports.map(closeTransportSafely));\n });\n transportCloseQueueRef.current = task;\n return task;\n };\n\n const closePendingTransport = async () => {\n const transport = pendingTransportRef.current;\n pendingTransportRef.current = null;\n await closeQueuedTransports(transport ? [transport] : []);\n };\n\n const resolvePendingElicitation = (id: string, result: ElicitResult) => {\n const entry = elicitationResolversRef.current.get(id);\n if (!entry) return false;\n elicitationResolversRef.current.delete(id);\n entry.signal.removeEventListener(\"abort\", entry.onAbort);\n setPendingElicitations((current) =>\n current.filter((elicitation) => elicitation.id !== id),\n );\n entry.resolve(result);\n return true;\n };\n\n const setPendingElicitationError = (\n id: string,\n error: NonNullable<MCPElicitation[\"error\"]>,\n ) => {\n if (!elicitationResolversRef.current.has(id)) return false;\n setPendingElicitations((current) =>\n current.map((elicitation) =>\n elicitation.id === id ? { ...elicitation, error } : elicitation,\n ),\n );\n return true;\n };\n\n const cancelPendingElicitations = () => {\n for (const [id] of elicitationResolversRef.current) {\n resolvePendingElicitation(id, { action: \"cancel\" });\n }\n };\n\n const closeTransports = async () => {\n cancelPendingElicitations();\n const pendingTransport = pendingTransportRef.current;\n const activeTransport = transportRef.current;\n pendingTransportRef.current = null;\n transportRef.current = null;\n clientRef.current = null;\n\n const transports = new Set(\n [pendingTransport, activeTransport].filter(\n (transport): transport is StreamableHTTPClientTransport =>\n transport !== null,\n ),\n );\n await closeQueuedTransports([...transports]);\n };\n\n const isCurrentConnection = (generation: number) =>\n mountedRef.current && generation === connectionGenerationRef.current;\n\n const createInterruptedAuthError = (cause?: unknown) =>\n new Error(\n `MCP server \"${props.id}\" authorization was interrupted before completion.`,\n cause === undefined ? undefined : { cause },\n );\n\n const withConnectionTimeout = useEffectEvent(\n async <T>(\n promise: Promise<T>,\n phase: \"connecting\" | \"listing tools\",\n startedAt: number,\n ): Promise<T> => {\n const timeoutMs = props.connectionTimeout;\n if (timeoutMs === undefined) return await promise;\n const remainingMs = timeoutMs - (Date.now() - startedAt);\n const timeoutError = () =>\n new Error(\n `MCP server \"${props.id}\" timed out while ${phase} after ${timeoutMs}ms.`,\n );\n if (remainingMs <= 0) throw timeoutError();\n\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timeout = setTimeout(() => reject(timeoutError()), remainingMs);\n }),\n ]);\n } finally {\n if (timeout !== undefined) clearTimeout(timeout);\n }\n },\n );\n\n const buildTransport = useEffectEvent(\n async (): Promise<StreamableHTTPClientTransport> => {\n if (props.auth.type === \"oauth\") {\n const authProvider = createOAuthProvider({\n serverId: props.id,\n config: props.auth,\n storage: props.storage,\n redirectUri: props.redirectUri,\n onAuthorizationUrl: (url) => setAuthorizationUrl(url.toString()),\n });\n return new StreamableHTTPClientTransport(new URL(props.url), {\n authProvider,\n });\n }\n if (props.auth.type === \"bearer\") {\n const persisted = await props.storage.loadAuthState(props.id);\n const headers = buildHeaders(props.auth, persisted);\n const transportOpts: StreamableHTTPClientTransportOptions = {};\n if (headers) transportOpts.requestInit = { headers };\n return new StreamableHTTPClientTransport(\n new URL(props.url),\n transportOpts,\n );\n }\n return new StreamableHTTPClientTransport(new URL(props.url));\n },\n );\n\n const applyToolsList = useEffectEvent(\n (list: {\n tools: Array<{\n name: string;\n description?: string | undefined;\n inputSchema: unknown;\n }>;\n }) => {\n setTools(\n list.tools.map((t) => {\n const info: MCPToolInfo = {\n name: t.name,\n inputSchema: t.inputSchema,\n };\n if (t.description !== undefined) info.description = t.description;\n return info;\n }),\n );\n },\n );\n\n const syncTools = useEffectEvent(\n async (\n client: Client,\n generation: number,\n options?: { startedAt?: number | undefined },\n ): Promise<boolean> => {\n const listPromise = client.listTools();\n const list =\n options?.startedAt === undefined\n ? await listPromise\n : await withConnectionTimeout(\n listPromise,\n \"listing tools\",\n options.startedAt,\n );\n if (!isCurrentConnection(generation)) return false;\n applyToolsList(list);\n return true;\n },\n );\n\n const finalizeConnect = useEffectEvent(\n async (\n transport: StreamableHTTPClientTransport,\n generation: number,\n ): Promise<boolean> => {\n const clientOptions: ClientOptions = {\n ...(props.elicitation === false\n ? {}\n : { capabilities: { elicitation: {} } }),\n listChanged: {\n tools: {\n autoRefresh: true,\n debounceMs: 300,\n onChanged: (error, items) => {\n if (!isCurrentConnection(generation)) return;\n if (error !== null) {\n setLastError({ message: error.message });\n return;\n }\n if (items === null) return;\n setLastError(null);\n applyToolsList({ tools: items });\n },\n },\n },\n };\n if (props.cache?.defaultTtlMs !== undefined) {\n clientOptions.defaultCacheTtlMs = props.cache.defaultTtlMs;\n }\n const client = new Client(\n {\n name: \"assistant-ui-mcp\",\n version: \"0.0.0\",\n },\n clientOptions,\n );\n if (props.elicitation !== false) {\n client.setRequestHandler(\n \"elicitation/create\",\n (request: ElicitRequest, context): Promise<ElicitResult> => {\n if (!isCurrentConnection(generation)) {\n return Promise.resolve({ action: \"cancel\" });\n }\n if (!(\"requestedSchema\" in request.params)) {\n return Promise.resolve({ action: \"cancel\" });\n }\n const { message, requestedSchema } = request.params;\n\n const id =\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : `mcp-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n const promise = new Promise<ElicitResult>((resolve) => {\n const onAbort = () => {\n resolvePendingElicitation(id, { action: \"cancel\" });\n };\n elicitationResolversRef.current.set(id, {\n resolve,\n signal: context.signal,\n onAbort,\n requestedSchema,\n });\n });\n setPendingElicitations((current) => [\n ...current,\n {\n id,\n message,\n requestedSchema,\n },\n ]);\n const entry = elicitationResolversRef.current.get(id);\n if (entry) {\n if (context.signal.aborted) {\n entry.onAbort();\n } else {\n context.signal.addEventListener(\"abort\", entry.onAbort, {\n once: true,\n });\n }\n }\n return promise;\n },\n );\n }\n const startedAt = Date.now();\n await withConnectionTimeout(\n client.connect(transport),\n \"connecting\",\n startedAt,\n );\n if (!isCurrentConnection(generation)) return false;\n // Defer ref assignment until listTools() also succeeds — otherwise a\n // post-connect failure leaves stale refs that `callTool()` would\n // happily walk into, producing confusing SDK errors instead of\n // \"not connected\".\n const synced = await syncTools(client, generation, { startedAt });\n if (!synced) return false;\n\n pendingTransportRef.current = null;\n clientRef.current = client;\n transportRef.current = transport;\n setConnectionState(\"connected\");\n return true;\n },\n );\n\n const doConnect = useEffectEvent(async () => {\n const generation = ++connectionGenerationRef.current;\n // Close any prior transport/client so a re-connect doesn't leak.\n await closeTransports();\n if (!isCurrentConnection(generation)) return;\n\n setConnectionState(\"connecting\");\n setLastError(null);\n setAuthorizationUrl(null);\n // Clear tools so a reconnect (error → connect, or authRequired\n // → connect) doesn't expose the previous attempt's tool list.\n setTools([]);\n let transport: StreamableHTTPClientTransport | null = null;\n try {\n transport = await buildTransport();\n if (!isCurrentConnection(generation)) {\n await closeQueuedTransports([transport]);\n return;\n }\n pendingTransportRef.current = transport;\n // Don't assign to transportRef until connect succeeds — otherwise a\n // failed `listTools()` leaves an orphaned transport that future\n // doConnect / doDisconnect calls treat as live.\n await finalizeConnect(transport, generation);\n } catch (err) {\n if (!isCurrentConnection(generation)) return;\n\n if (err instanceof UnauthorizedError) {\n // OAuth: keep the transport alive so completeAuth can call\n // finishAuth on it. Closing it before storing would leave a\n // closed transport on transportRef.\n pendingTransportRef.current = null;\n transportRef.current = transport;\n setConnectionState(\"authRequired\");\n } else {\n cancelPendingElicitations();\n if (transport) {\n pendingTransportRef.current = null;\n await closeQueuedTransports([transport]);\n }\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n setConnectionState(\"error\");\n }\n }\n });\n\n const doDisconnect = useEffectEvent(async () => {\n connectionGenerationRef.current += 1;\n setTools([]);\n setAuthorizationUrl(null);\n setConnectionState(\"disconnected\");\n await closeTransports();\n });\n\n const doCompleteAuth = useEffectEvent(async (callbackUrl: string) => {\n const generation = ++connectionGenerationRef.current;\n cancelPendingElicitations();\n await closePendingTransport();\n if (!isCurrentConnection(generation)) throw createInterruptedAuthError();\n\n setConnectionState(\"authPending\");\n setLastError(null);\n try {\n const url = new URL(callbackUrl);\n const code = url.searchParams.get(\"code\");\n if (!code) throw new Error(\"missing authorization code in callback URL\");\n let transport = transportRef.current;\n if (!transport) {\n transport = await buildTransport();\n if (!isCurrentConnection(generation)) {\n await closeQueuedTransports([transport]);\n throw createInterruptedAuthError();\n }\n }\n transportRef.current = null;\n clientRef.current = null;\n pendingTransportRef.current = transport;\n await transport.finishAuth(code);\n if (!isCurrentConnection(generation)) throw createInterruptedAuthError();\n setAuthorizationUrl(null);\n const connected = await finalizeConnect(transport, generation);\n if (!connected) throw createInterruptedAuthError();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (!isCurrentConnection(generation))\n throw createInterruptedAuthError(error);\n\n await closeTransports();\n setLastError({\n message: error.message,\n });\n setConnectionState(\"error\");\n throw error;\n }\n });\n\n const tryAutoConnect = useEffectEvent(\n async (signal: { cancelled: boolean }) => {\n if (!props.autoConnect) return;\n if (props.auth.type === \"none\") {\n void doConnect();\n return;\n }\n // Static connector token short-circuits the storage read.\n if (props.auth.type === \"bearer\" && props.auth.token) {\n void doConnect();\n return;\n }\n const generation = connectionGenerationRef.current;\n let persisted: Awaited<ReturnType<MCPStorage[\"loadAuthState\"]>>;\n try {\n persisted = await props.storage.loadAuthState(props.id);\n } catch (error) {\n if (signal.cancelled || !isCurrentConnection(generation)) return;\n const message = error instanceof Error ? error.message : String(error);\n setLastError({\n message: `MCP server \"${props.id}\" failed to load saved authentication: ${message}`,\n });\n setConnectionState(\"error\");\n return;\n }\n if (signal.cancelled || !isCurrentConnection(generation)) return;\n if (props.auth.type === \"oauth\") {\n if (!persisted?.tokens) return;\n } else if (!persisted?.token) {\n return;\n }\n void doConnect();\n },\n );\n\n // Auto-connect on mount when usable auth exists.\n useEffect(() => {\n const previousDisposal = pendingDisposalRef.current;\n if (previousDisposal) previousDisposal.cancelled = true;\n const pendingDisposal = { cancelled: false };\n pendingDisposalRef.current = pendingDisposal;\n mountedRef.current = true;\n const signal = { cancelled: false };\n void tryAutoConnect(signal);\n return () => {\n mountedRef.current = false;\n signal.cancelled = true;\n // Defer disposal so StrictMode can replay setup before closing the transport.\n queueMicrotask(() => {\n if (pendingDisposal.cancelled) return;\n connectionGenerationRef.current += 1;\n void closeTransports();\n });\n };\n }, []);\n\n const state = useMemo<MCPServerState>(\n () => ({\n id: props.id,\n kind: props.kind,\n name: props.name,\n url: props.url,\n icon: props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n pendingElicitations,\n }),\n [\n props.id,\n props.kind,\n props.name,\n props.url,\n props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n pendingElicitations,\n ],\n );\n\n return {\n getState: () => state,\n connect: doConnect,\n disconnect: doDisconnect,\n remove: async () => {\n await doDisconnect();\n try {\n await props.storage.clearAuthState(props.id);\n await props.onRemove();\n } catch (err) {\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n throw err;\n }\n },\n callTool: async (name, args) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.callTool({\n name,\n arguments: args as Record<string, unknown> | undefined,\n });\n },\n listResources: async (params) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.listResources(params);\n },\n readResource: async (uri) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.readResource({ uri });\n },\n completeAuth: doCompleteAuth,\n answerElicitation: (\n id: string,\n response: MCPElicitationResponse,\n ): readonly { property: string; message: string }[] | undefined => {\n if (response.action === \"accept\") {\n const entry = elicitationResolversRef.current.get(id);\n if (!entry) return;\n\n if (\n typeof response.content !== \"object\" ||\n response.content === null ||\n Array.isArray(response.content)\n ) {\n const errors = [\n {\n property: \"content\",\n message: \"Response content must be an object.\",\n },\n ];\n setPendingElicitationError(id, {\n message: \"Invalid elicitation content: content.\",\n properties: [\"content\"],\n });\n return errors;\n }\n\n const errors = validateElicitationContent(\n entry.requestedSchema,\n response.content,\n );\n if (errors.length > 0) {\n const properties = [\n ...new Set(errors.map((error) => error.property)),\n ];\n setPendingElicitationError(id, {\n message: `Invalid elicitation content: ${properties.join(\", \")}.`,\n properties,\n });\n return errors;\n }\n\n const result: ElicitResult = {\n action: \"accept\",\n content: response.content as ElicitResult[\"content\"],\n };\n resolvePendingElicitation(id, result);\n return;\n }\n\n resolvePendingElicitation(id, { action: response.action });\n },\n };\n};\n\nexport const McpServerResource = resource(useMcpServerResource);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"McpServerResource.js","names":["useState","useRef","useEffect","useMemo","useEffectEvent","resource","useResource","withKey","ClientOutput","Client","StreamableHTTPClientTransport","UnauthorizedError","ClientOptions","ElicitRequest","ElicitResult","StreamableHTTPClientTransportOptions","createOAuthProvider","buildHeaders","assertValidServerId","validateElicitationContent","MCPStorage","MCPAuthConfig","MCPConnectionState","MCPElicitation","MCPElicitationResponse","MCPServerKind","MCPServerState","MCPToolInfo","createMcpId","McpServerResourceProps","id","kind","name","url","icon","auth","storage","redirectUri","autoConnect","connectionTimeout","cache","defaultTtlMs","elicitation","onRemove","Promise","McpServerResourceInstanceProps","transportCloseQueueRef","current","getConnectionDependencies","props","authDependencies","type","token","scopes","length","authorizationEndpoint","tokenEndpoint","registrationEndpoint","clientId","clientSecret","areConnectionDependenciesEqual","left","right","every","value","index","Object","is","useMcpServerResourceInstance","connectionState","setConnectionState","tools","setTools","lastError","setLastError","message","authorizationUrl","setAuthorizationUrl","pendingElicitations","setPendingElicitations","clientRef","transportRef","pendingTransportRef","connectionGenerationRef","elicitationResolversRef","Map","resolve","result","signal","AbortSignal","onAbort","requestedSchema","pendingDisposalRef","cancelled","mountedRef","closeTransportSafely","transport","close","closeQueuedTransports","transports","task","then","all","map","closePendingTransport","resolvePendingElicitation","entry","get","delete","removeEventListener","filter","setPendingElicitationError","error","NonNullable","has","cancelPendingElicitations","action","detachTransports","pendingTransport","activeTransport","Set","closeTransports","isCurrentConnection","generation","createInterruptedAuthError","cause","Error","undefined","withConnectionTimeout","promise","T","phase","startedAt","timeoutMs","remainingMs","Date","now","timeoutError","timeout","ReturnType","setTimeout","race","_","reject","clearTimeout","buildTransport","authProvider","serverId","config","onAuthorizationUrl","toString","URL","persisted","loadAuthState","headers","transportOpts","requestInit","applyToolsList","list","Array","description","inputSchema","t","info","syncTools","client","options","listPromise","listTools","finalizeConnect","clientOptions","capabilities","listChanged","autoRefresh","debounceMs","onChanged","items","defaultCacheTtlMs","version","setRequestHandler","request","context","params","set","aborted","addEventListener","once","connect","synced","doConnect","err","String","doDisconnect","doCompleteAuth","callbackUrl","code","searchParams","finishAuth","connected","tryAutoConnect","Awaited","tokens","previousDisposal","pendingDisposal","queueMicrotask","state","getState","disconnect","remove","clearAuthState","callTool","args","arguments","Record","listResources","readResource","uri","completeAuth","answerElicitation","response","property","content","isArray","errors","properties","join","McpServerResourceInstance","McpServerResource","useMcpServerResource","dependencies","connection","setConnection","currentConnection"],"sources":["../../src/resources/McpServerResource.ts"],"sourcesContent":["import { useState, useRef, useEffect, useMemo, useEffectEvent } from \"react\";\nimport { resource, useResource, withKey } from \"@assistant-ui/tap\";\nimport type { ClientOutput } from \"@assistant-ui/store\";\nimport {\n Client,\n StreamableHTTPClientTransport,\n UnauthorizedError,\n type ClientOptions,\n type ElicitRequest,\n type ElicitResult,\n type StreamableHTTPClientTransportOptions,\n} from \"@modelcontextprotocol/client\";\nimport { createOAuthProvider } from \"../auth/createOAuthProvider\";\nimport { buildHeaders } from \"../auth/buildHeaders\";\nimport { assertValidServerId } from \"../utils/serverId\";\nimport { validateElicitationContent } from \"./validateElicitationContent\";\nimport type { MCPStorage } from \"./storage/types\";\nimport type {\n MCPAuthConfig,\n MCPConnectionState,\n MCPElicitation,\n MCPElicitationResponse,\n MCPServerKind,\n MCPServerState,\n MCPToolInfo,\n} from \"../mcp-scope\";\nimport { createMcpId } from \"../utils/createMcpId\";\n\nexport type McpServerResourceProps = {\n id: string;\n kind: MCPServerKind;\n name: string;\n url: string;\n icon?: string | undefined;\n auth: MCPAuthConfig;\n storage: MCPStorage;\n redirectUri: string;\n autoConnect: boolean;\n connectionTimeout?: number | undefined;\n cache?: { readonly defaultTtlMs?: number } | undefined;\n readonly elicitation?: boolean;\n onRemove: () => Promise<void>;\n};\n\ntype McpServerResourceInstanceProps = McpServerResourceProps & {\n transportCloseQueueRef: { current: Promise<void> };\n};\n\nconst getConnectionDependencies = (\n props: McpServerResourceProps,\n): readonly unknown[] => {\n const auth = props.auth;\n const authDependencies =\n auth.type === \"bearer\"\n ? [auth.type, auth.token]\n : auth.type === \"oauth\"\n ? [\n auth.type,\n auth.scopes?.length,\n ...(auth.scopes ?? []),\n auth.authorizationEndpoint,\n auth.tokenEndpoint,\n auth.registrationEndpoint,\n auth.clientId,\n auth.clientSecret,\n ]\n : [auth.type];\n\n return [\n props.id,\n props.url,\n ...authDependencies,\n props.redirectUri,\n props.cache?.defaultTtlMs,\n props.elicitation !== false,\n ];\n};\n\nconst areConnectionDependenciesEqual = (\n left: readonly unknown[],\n right: readonly unknown[],\n) =>\n left.length === right.length &&\n left.every((value, index) => Object.is(value, right[index]));\n\nconst useMcpServerResourceInstance = (\n props: McpServerResourceInstanceProps,\n): ClientOutput<\"mcpServer\"> => {\n assertValidServerId(props.id);\n const [connectionState, setConnectionState] =\n useState<MCPConnectionState>(\"disconnected\");\n const [tools, setTools] = useState<MCPToolInfo[]>([]);\n const [lastError, setLastError] = useState<{ message: string } | null>(null);\n const [authorizationUrl, setAuthorizationUrl] = useState<string | null>(null);\n const [pendingElicitations, setPendingElicitations] = useState<\n MCPElicitation[]\n >([]);\n\n const clientRef = useRef<Client | null>(null);\n const transportRef = useRef<StreamableHTTPClientTransport | null>(null);\n const pendingTransportRef = useRef<StreamableHTTPClientTransport | null>(\n null,\n );\n const connectionGenerationRef = useRef(0);\n const elicitationResolversRef = useRef(\n new Map<\n string,\n {\n resolve: (result: ElicitResult) => void;\n signal: AbortSignal;\n onAbort: () => void;\n requestedSchema: unknown;\n }\n >(),\n );\n const pendingDisposalRef = useRef<{ cancelled: boolean } | null>(null);\n const mountedRef = useRef(true);\n\n const closeTransportSafely = async (\n transport: StreamableHTTPClientTransport,\n ): Promise<void> => {\n try {\n await transport.close();\n } catch {\n // ignore close errors\n }\n };\n\n const closeQueuedTransports = (\n transports: StreamableHTTPClientTransport[],\n ): Promise<void> => {\n const task = props.transportCloseQueueRef.current.then(async () => {\n await Promise.all(transports.map(closeTransportSafely));\n });\n props.transportCloseQueueRef.current = task;\n return task;\n };\n\n const closePendingTransport = async () => {\n const transport = pendingTransportRef.current;\n pendingTransportRef.current = null;\n await closeQueuedTransports(transport ? [transport] : []);\n };\n\n const resolvePendingElicitation = (id: string, result: ElicitResult) => {\n const entry = elicitationResolversRef.current.get(id);\n if (!entry) return false;\n elicitationResolversRef.current.delete(id);\n entry.signal.removeEventListener(\"abort\", entry.onAbort);\n setPendingElicitations((current) =>\n current.filter((elicitation) => elicitation.id !== id),\n );\n entry.resolve(result);\n return true;\n };\n\n const setPendingElicitationError = (\n id: string,\n error: NonNullable<MCPElicitation[\"error\"]>,\n ) => {\n if (!elicitationResolversRef.current.has(id)) return false;\n setPendingElicitations((current) =>\n current.map((elicitation) =>\n elicitation.id === id ? { ...elicitation, error } : elicitation,\n ),\n );\n return true;\n };\n\n const cancelPendingElicitations = () => {\n for (const [id] of elicitationResolversRef.current) {\n resolvePendingElicitation(id, { action: \"cancel\" });\n }\n };\n\n const detachTransports = () => {\n cancelPendingElicitations();\n const pendingTransport = pendingTransportRef.current;\n const activeTransport = transportRef.current;\n pendingTransportRef.current = null;\n transportRef.current = null;\n clientRef.current = null;\n\n return [\n ...new Set(\n [pendingTransport, activeTransport].filter(\n (transport): transport is StreamableHTTPClientTransport =>\n transport !== null,\n ),\n ),\n ];\n };\n\n const closeTransports = async () => {\n await closeQueuedTransports(detachTransports());\n };\n\n const isCurrentConnection = (generation: number) =>\n mountedRef.current && generation === connectionGenerationRef.current;\n\n const createInterruptedAuthError = (cause?: unknown) =>\n new Error(\n `MCP server \"${props.id}\" authorization was interrupted before completion.`,\n cause === undefined ? undefined : { cause },\n );\n\n const withConnectionTimeout = useEffectEvent(\n async <T>(\n promise: Promise<T>,\n phase: \"connecting\" | \"listing tools\",\n startedAt: number,\n ): Promise<T> => {\n const timeoutMs = props.connectionTimeout;\n if (timeoutMs === undefined) return await promise;\n const remainingMs = timeoutMs - (Date.now() - startedAt);\n const timeoutError = () =>\n new Error(\n `MCP server \"${props.id}\" timed out while ${phase} after ${timeoutMs}ms.`,\n );\n if (remainingMs <= 0) throw timeoutError();\n\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timeout = setTimeout(() => reject(timeoutError()), remainingMs);\n }),\n ]);\n } finally {\n if (timeout !== undefined) clearTimeout(timeout);\n }\n },\n );\n\n const buildTransport = useEffectEvent(\n async (): Promise<StreamableHTTPClientTransport> => {\n if (props.auth.type === \"oauth\") {\n const authProvider = createOAuthProvider({\n serverId: props.id,\n config: props.auth,\n storage: props.storage,\n redirectUri: props.redirectUri,\n onAuthorizationUrl: (url) => setAuthorizationUrl(url.toString()),\n });\n return new StreamableHTTPClientTransport(new URL(props.url), {\n authProvider,\n });\n }\n if (props.auth.type === \"bearer\") {\n const persisted = await props.storage.loadAuthState(props.id);\n const headers = buildHeaders(props.auth, persisted);\n const transportOpts: StreamableHTTPClientTransportOptions = {};\n if (headers) transportOpts.requestInit = { headers };\n return new StreamableHTTPClientTransport(\n new URL(props.url),\n transportOpts,\n );\n }\n return new StreamableHTTPClientTransport(new URL(props.url));\n },\n );\n\n const applyToolsList = useEffectEvent(\n (list: {\n tools: Array<{\n name: string;\n description?: string | undefined;\n inputSchema: unknown;\n }>;\n }) => {\n setTools(\n list.tools.map((t) => {\n const info: MCPToolInfo = {\n name: t.name,\n inputSchema: t.inputSchema,\n };\n if (t.description !== undefined) info.description = t.description;\n return info;\n }),\n );\n },\n );\n\n const syncTools = useEffectEvent(\n async (\n client: Client,\n generation: number,\n options?: { startedAt?: number | undefined },\n ): Promise<boolean> => {\n const listPromise = client.listTools();\n const list =\n options?.startedAt === undefined\n ? await listPromise\n : await withConnectionTimeout(\n listPromise,\n \"listing tools\",\n options.startedAt,\n );\n if (!isCurrentConnection(generation)) return false;\n applyToolsList(list);\n return true;\n },\n );\n\n const finalizeConnect = useEffectEvent(\n async (\n transport: StreamableHTTPClientTransport,\n generation: number,\n ): Promise<boolean> => {\n const clientOptions: ClientOptions = {\n ...(props.elicitation === false\n ? {}\n : { capabilities: { elicitation: {} } }),\n listChanged: {\n tools: {\n autoRefresh: true,\n debounceMs: 300,\n onChanged: (error, items) => {\n if (!isCurrentConnection(generation)) return;\n if (error !== null) {\n setLastError({ message: error.message });\n return;\n }\n if (items === null) return;\n setLastError(null);\n applyToolsList({ tools: items });\n },\n },\n },\n };\n if (props.cache?.defaultTtlMs !== undefined) {\n clientOptions.defaultCacheTtlMs = props.cache.defaultTtlMs;\n }\n const client = new Client(\n {\n name: \"assistant-ui-mcp\",\n version: \"0.0.0\",\n },\n clientOptions,\n );\n if (props.elicitation !== false) {\n client.setRequestHandler(\n \"elicitation/create\",\n (request: ElicitRequest, context): Promise<ElicitResult> => {\n if (!isCurrentConnection(generation)) {\n return Promise.resolve({ action: \"cancel\" });\n }\n if (!(\"requestedSchema\" in request.params)) {\n return Promise.resolve({ action: \"cancel\" });\n }\n const { message, requestedSchema } = request.params;\n\n const id = createMcpId();\n const promise = new Promise<ElicitResult>((resolve) => {\n const onAbort = () => {\n resolvePendingElicitation(id, { action: \"cancel\" });\n };\n elicitationResolversRef.current.set(id, {\n resolve,\n signal: context.signal,\n onAbort,\n requestedSchema,\n });\n });\n setPendingElicitations((current) => [\n ...current,\n {\n id,\n message,\n requestedSchema,\n },\n ]);\n const entry = elicitationResolversRef.current.get(id);\n if (entry) {\n if (context.signal.aborted) {\n entry.onAbort();\n } else {\n context.signal.addEventListener(\"abort\", entry.onAbort, {\n once: true,\n });\n }\n }\n return promise;\n },\n );\n }\n const startedAt = Date.now();\n await withConnectionTimeout(\n client.connect(transport),\n \"connecting\",\n startedAt,\n );\n if (!isCurrentConnection(generation)) return false;\n // Defer ref assignment until listTools() also succeeds — otherwise a\n // post-connect failure leaves stale refs that `callTool()` would\n // happily walk into, producing confusing SDK errors instead of\n // \"not connected\".\n const synced = await syncTools(client, generation, { startedAt });\n if (!synced) return false;\n\n pendingTransportRef.current = null;\n clientRef.current = client;\n transportRef.current = transport;\n setConnectionState(\"connected\");\n return true;\n },\n );\n\n const doConnect = useEffectEvent(async () => {\n const generation = ++connectionGenerationRef.current;\n // Close any prior transport/client so a re-connect doesn't leak.\n await closeTransports();\n if (!isCurrentConnection(generation)) return;\n\n setConnectionState(\"connecting\");\n setLastError(null);\n setAuthorizationUrl(null);\n // Clear tools so a reconnect (error → connect, or authRequired\n // → connect) doesn't expose the previous attempt's tool list.\n setTools([]);\n let transport: StreamableHTTPClientTransport | null = null;\n try {\n transport = await buildTransport();\n if (!isCurrentConnection(generation)) {\n await closeQueuedTransports([transport]);\n return;\n }\n pendingTransportRef.current = transport;\n // Don't assign to transportRef until connect succeeds — otherwise a\n // failed `listTools()` leaves an orphaned transport that future\n // doConnect / doDisconnect calls treat as live.\n await finalizeConnect(transport, generation);\n } catch (err) {\n if (!isCurrentConnection(generation)) return;\n\n if (err instanceof UnauthorizedError) {\n // OAuth: keep the transport alive so completeAuth can call\n // finishAuth on it. Closing it before storing would leave a\n // closed transport on transportRef.\n pendingTransportRef.current = null;\n transportRef.current = transport;\n setConnectionState(\"authRequired\");\n } else {\n cancelPendingElicitations();\n if (transport) {\n pendingTransportRef.current = null;\n await closeQueuedTransports([transport]);\n }\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n setConnectionState(\"error\");\n }\n }\n });\n\n const doDisconnect = useEffectEvent(async () => {\n connectionGenerationRef.current += 1;\n setTools([]);\n setAuthorizationUrl(null);\n setConnectionState(\"disconnected\");\n await closeTransports();\n });\n\n const doCompleteAuth = useEffectEvent(async (callbackUrl: string) => {\n const generation = ++connectionGenerationRef.current;\n cancelPendingElicitations();\n await closePendingTransport();\n if (!isCurrentConnection(generation)) throw createInterruptedAuthError();\n\n setConnectionState(\"authPending\");\n setLastError(null);\n try {\n const url = new URL(callbackUrl);\n const code = url.searchParams.get(\"code\");\n if (!code) throw new Error(\"missing authorization code in callback URL\");\n let transport = transportRef.current;\n if (!transport) {\n transport = await buildTransport();\n if (!isCurrentConnection(generation)) {\n await closeQueuedTransports([transport]);\n throw createInterruptedAuthError();\n }\n }\n transportRef.current = null;\n clientRef.current = null;\n pendingTransportRef.current = transport;\n await transport.finishAuth(code);\n if (!isCurrentConnection(generation)) throw createInterruptedAuthError();\n setAuthorizationUrl(null);\n const connected = await finalizeConnect(transport, generation);\n if (!connected) throw createInterruptedAuthError();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (!isCurrentConnection(generation))\n throw createInterruptedAuthError(error);\n\n await closeTransports();\n setLastError({\n message: error.message,\n });\n setConnectionState(\"error\");\n throw error;\n }\n });\n\n const tryAutoConnect = useEffectEvent(\n async (signal: { cancelled: boolean }) => {\n if (!props.autoConnect) return;\n if (props.auth.type === \"none\") {\n void doConnect();\n return;\n }\n // Static connector token short-circuits the storage read.\n if (props.auth.type === \"bearer\" && props.auth.token) {\n void doConnect();\n return;\n }\n const generation = connectionGenerationRef.current;\n let persisted: Awaited<ReturnType<MCPStorage[\"loadAuthState\"]>>;\n try {\n persisted = await props.storage.loadAuthState(props.id);\n } catch (error) {\n if (signal.cancelled || !isCurrentConnection(generation)) return;\n const message = error instanceof Error ? error.message : String(error);\n setLastError({\n message: `MCP server \"${props.id}\" failed to load saved authentication: ${message}`,\n });\n setConnectionState(\"error\");\n return;\n }\n if (signal.cancelled || !isCurrentConnection(generation)) return;\n if (props.auth.type === \"oauth\") {\n if (!persisted?.tokens) return;\n } else if (!persisted?.token) {\n return;\n }\n void doConnect();\n },\n );\n\n // Auto-connect on mount when usable auth exists.\n useEffect(() => {\n const transportCloseQueueRef = props.transportCloseQueueRef;\n const previousDisposal = pendingDisposalRef.current;\n if (previousDisposal) previousDisposal.cancelled = true;\n const pendingDisposal = { cancelled: false };\n pendingDisposalRef.current = pendingDisposal;\n mountedRef.current = true;\n const signal = { cancelled: false };\n void tryAutoConnect(signal);\n return () => {\n mountedRef.current = false;\n signal.cancelled = true;\n const task = transportCloseQueueRef.current.then(async () => {\n await new Promise<void>((resolve) => queueMicrotask(resolve));\n if (pendingDisposal.cancelled) return;\n connectionGenerationRef.current += 1;\n await Promise.all(detachTransports().map(closeTransportSafely));\n });\n transportCloseQueueRef.current = task;\n };\n }, [props.transportCloseQueueRef]);\n\n const state = useMemo<MCPServerState>(\n () => ({\n id: props.id,\n kind: props.kind,\n name: props.name,\n url: props.url,\n icon: props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n pendingElicitations,\n }),\n [\n props.id,\n props.kind,\n props.name,\n props.url,\n props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n pendingElicitations,\n ],\n );\n\n return {\n getState: () => state,\n connect: doConnect,\n disconnect: doDisconnect,\n remove: async () => {\n await doDisconnect();\n try {\n await props.storage.clearAuthState(props.id);\n await props.onRemove();\n } catch (err) {\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n throw err;\n }\n },\n callTool: async (name, args) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.callTool({\n name,\n arguments: args as Record<string, unknown> | undefined,\n });\n },\n listResources: async (params) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.listResources(params);\n },\n readResource: async (uri) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.readResource({ uri });\n },\n completeAuth: doCompleteAuth,\n answerElicitation: (\n id: string,\n response: MCPElicitationResponse,\n ): readonly { property: string; message: string }[] | undefined => {\n if (response.action === \"accept\") {\n const entry = elicitationResolversRef.current.get(id);\n if (!entry) return;\n\n if (\n typeof response.content !== \"object\" ||\n response.content === null ||\n Array.isArray(response.content)\n ) {\n const errors = [\n {\n property: \"content\",\n message: \"Response content must be an object.\",\n },\n ];\n setPendingElicitationError(id, {\n message: \"Invalid elicitation content: content.\",\n properties: [\"content\"],\n });\n return errors;\n }\n\n const errors = validateElicitationContent(\n entry.requestedSchema,\n response.content,\n );\n if (errors.length > 0) {\n const properties = [\n ...new Set(errors.map((error) => error.property)),\n ];\n setPendingElicitationError(id, {\n message: `Invalid elicitation content: ${properties.join(\", \")}.`,\n properties,\n });\n return errors;\n }\n\n const result: ElicitResult = {\n action: \"accept\",\n content: response.content as ElicitResult[\"content\"],\n };\n resolvePendingElicitation(id, result);\n return;\n }\n\n resolvePendingElicitation(id, { action: response.action });\n },\n };\n};\n\nconst McpServerResourceInstance = resource(useMcpServerResourceInstance);\n\nexport const McpServerResource = resource(function useMcpServerResource(\n props: McpServerResourceProps,\n): ClientOutput<\"mcpServer\"> {\n const transportCloseQueueRef = useRef(Promise.resolve());\n const dependencies = getConnectionDependencies(props);\n const [connection, setConnection] = useState({ dependencies, generation: 0 });\n let currentConnection = connection;\n if (!areConnectionDependenciesEqual(connection.dependencies, dependencies)) {\n currentConnection = {\n dependencies,\n generation: connection.generation + 1,\n };\n setConnection(currentConnection);\n }\n\n // Storage resources do not expose a stable scope identity and may return a\n // fresh client on ordinary renders, so storage changes cannot key remounts.\n return useResource(\n withKey(\n currentConnection.generation,\n McpServerResourceInstance({ ...props, transportCloseQueueRef }),\n ),\n );\n});\n"],"mappings":";;;;;;;;;AAgDA,MAAMgD,6BACJC,UACuB;CACvB,MAAMd,OAAOc,MAAMd;CACnB,MAAMe,mBACJf,KAAKgB,SAAS,WACV,CAAChB,KAAKgB,MAAMhB,KAAKiB,KAAK,IACtBjB,KAAKgB,SAAS,UACZ;EACEhB,KAAKgB;EACLhB,KAAKkB,QAAQC;EACb,GAAInB,KAAKkB,UAAU,CAAA;EACnBlB,KAAKoB;EACLpB,KAAKqB;EACLrB,KAAKsB;EACLtB,KAAKuB;EACLvB,KAAKwB;CAAY,IAEnB,CAACxB,KAAKgB,IAAI;CAElB,OAAO;EACLF,MAAMnB;EACNmB,MAAMhB;EACN,GAAGiB;EACHD,MAAMZ;EACNY,MAAMT,OAAOC;EACbQ,MAAMP,gBAAgB;CAAK;AAE/B;AAEA,MAAMkB,kCACJC,MACAC,UAEAD,KAAKP,WAAWQ,MAAMR,UACtBO,KAAKE,OAAOC,OAAOC,UAAUC,OAAOC,GAAGH,OAAOF,MAAMG,MAAM,CAAC;AAE7D,MAAMG,gCACJnB,UAC8B;CAC9B/B,oBAAoB+B,MAAMnB,EAAE;CAC5B,MAAM,CAACuC,iBAAiBC,sBACtBtE,SAA6B,cAAc;CAC7C,MAAM,CAACuE,OAAOC,YAAYxE,SAAwB,CAAA,CAAE;CACpD,MAAM,CAACyE,WAAWC,gBAAgB1E,SAAqC,IAAI;CAC3E,MAAM,CAAC4E,kBAAkBC,uBAAuB7E,SAAwB,IAAI;CAC5E,MAAM,CAAC8E,qBAAqBC,0BAA0B/E,SAEpD,CAAA,CAAE;CAEJ,MAAMgF,YAAY/E,OAAsB,IAAI;CAC5C,MAAMgF,eAAehF,OAA6C,IAAI;CACtE,MAAMiF,sBAAsBjF,OAC1B,IACF;CACA,MAAMkF,0BAA0BlF,OAAO,CAAC;CACxC,MAAMmF,0BAA0BnF,uBAC9B,IAAIoF,IAQF,CACJ;CACA,MAAMO,qBAAqB3F,OAAsC,IAAI;CACrE,MAAM6F,aAAa7F,OAAO,IAAI;CAE9B,MAAM8F,uBAAuB,OAC3BC,cACkB;EAClB,IAAI;GACF,MAAMA,UAAUC,MAAM;EACxB,QAAQ,CACN;CAEJ;CAEA,MAAMC,yBACJC,eACkB;EAClB,MAAMC,OAAOnD,MAAMH,uBAAuBC,QAAQsD,KAAK,YAAY;GACjE,MAAMzD,QAAQ0D,IAAIH,WAAWI,IAAIR,oBAAoB,CAAC;EACxD,CAAC;EACD9C,MAAMH,uBAAuBC,UAAUqD;EACvC,OAAOA;CACT;CAEA,MAAMI,wBAAwB,YAAY;EACxC,MAAMR,cAAYd,oBAAoBnC;EACtCmC,oBAAoBnC,UAAU;EAC9B,MAAMmD,sBAAsBF,cAAY,CAACA,WAAS,IAAI,CAAA,CAAE;CAC1D;CAEA,MAAMS,6BAA6B3E,IAAYyD,WAAyB;EACtE,MAAMmB,QAAQtB,wBAAwBrC,QAAQ4D,IAAI7E,EAAE;EACpD,IAAI,CAAC4E,OAAO,OAAO;EACnBtB,wBAAwBrC,QAAQ6D,OAAO9E,EAAE;EACzC4E,MAAMlB,OAAOqB,oBAAoB,SAASH,MAAMhB,OAAO;EACvDX,wBAAwBhC,YACtBA,QAAQ+D,QAAQpE,gBAAgBA,YAAYZ,OAAOA,EAAE,CACvD;EACA4E,MAAMpB,QAAQC,MAAM;EACpB,OAAO;CACT;CAEA,MAAMwB,8BACJjF,MACAkF,UACG;EACH,IAAI,CAAC5B,wBAAwBrC,QAAQmE,IAAIpF,IAAE,GAAG,OAAO;EACrDiD,wBAAwBhC,cACtBA,UAAQwD,KAAK7D,kBACXA,cAAYZ,OAAOA,OAAK;GAAE,GAAGY;GAAasE;EAAM,IAAItE,aACtD,CACF;EACA,OAAO;CACT;CAEA,MAAMyE,kCAAkC;EACtC,KAAK,MAAM,CAACrF,SAAOsD,wBAAwBrC,SACzC0D,0BAA0B3E,MAAI,EAAEsF,QAAQ,SAAS,CAAC;CAEtD;CAEA,MAAMC,yBAAyB;EAC7BF,0BAA0B;EAC1B,MAAMG,mBAAmBpC,oBAAoBnC;EAC7C,MAAMwE,kBAAkBtC,aAAalC;EACrCmC,oBAAoBnC,UAAU;EAC9BkC,aAAalC,UAAU;EACvBiC,UAAUjC,UAAU;EAEpB,OAAO,CACL,GAAG,IAAIyE,IACL,CAACF,kBAAkBC,eAAe,CAAC,CAACT,QACjCd,gBACCA,gBAAc,IAClB,CACF,CAAC;CAEL;CAEA,MAAMyB,kBAAkB,YAAY;EAClC,MAAMvB,sBAAsBmB,iBAAiB,CAAC;CAChD;CAEA,MAAMK,uBAAuBC,eAC3B7B,WAAW/C,WAAW4E,eAAexC,wBAAwBpC;CAE/D,MAAM6E,8BAA8BC,UAClC,IAAIC,MACF,eAAe7E,MAAMnB,GAAE,qDACvB+F,UAAUE,KAAAA,IAAYA,KAAAA,IAAY,EAAEF,MAAM,CAC5C;CAEF,MAAMG,wBAAwB5H,eAC5B,OACE6H,SACAE,OACAC,cACe;EACf,MAAMC,YAAYpF,MAAMV;EACxB,IAAI8F,cAAcN,KAAAA,GAAW,OAAO,MAAME;EAC1C,MAAMK,cAAcD,aAAaE,KAAKC,IAAI,IAAIJ;EAC9C,MAAMK,qCACJ,IAAIX,MACF,eAAe7E,MAAMnB,GAAE,oBAAqBqG,MAAK,SAAUE,UAAS,IACtE;EACF,IAAIC,eAAe,GAAG,MAAMG,aAAa;EAEzC,IAAIC;EACJ,IAAI;GACF,OAAO,MAAM9F,QAAQiG,KAAK,CACxBZ,SACA,IAAIrF,SAAgBkG,GAAGC,WAAW;IAChCL,UAAUE,iBAAiBG,OAAON,aAAa,CAAC,GAAGH,WAAW;GAChE,CAAC,CAAC,CACH;EACH,UAAU;GACR,IAAII,YAAYX,KAAAA,GAAWiB,aAAaN,OAAO;EACjD;CACF,CACF;CAEA,MAAMO,iBAAiB7I,eACrB,YAAoD;EAClD,IAAI6C,MAAMd,KAAKgB,SAAS,SAAS;GAC/B,MAAM+F,eAAelI,oBAAoB;IACvCmI,UAAUlG,MAAMnB;IAChBsH,QAAQnG,MAAMd;IACdC,SAASa,MAAMb;IACfC,aAAaY,MAAMZ;IACnBgH,qBAAqBpH,QAAQ4C,oBAAoB5C,IAAIqH,SAAS,CAAC;GACjE,CAAC;GACD,OAAO,IAAI5I,8BAA8B,IAAI6I,IAAItG,MAAMhB,GAAG,GAAG,EAC3DiH,aACF,CAAC;EACH;EACA,IAAIjG,MAAMd,KAAKgB,SAAS,UAAU;GAChC,MAAMqG,YAAY,MAAMvG,MAAMb,QAAQqH,cAAcxG,MAAMnB,EAAE;GAC5D,MAAM4H,UAAUzI,aAAagC,MAAMd,MAAMqH,SAAS;GAClD,MAAMG,gBAAsD,CAAC;GAC7D,IAAID,SAASC,cAAcC,cAAc,EAAEF,QAAQ;GACnD,OAAO,IAAIhJ,8BACT,IAAI6I,IAAItG,MAAMhB,GAAG,GACjB0H,aACF;EACF;EACA,OAAO,IAAIjJ,8BAA8B,IAAI6I,IAAItG,MAAMhB,GAAG,CAAC;CAC7D,CACF;CAEA,MAAM4H,iBAAiBzJ,gBACpB0J,SAMK;EACJtF,SACEsF,KAAKvF,MAAMgC,KAAK2D,MAAM;GACpB,MAAMC,OAAoB;IACxBnI,MAAMkI,EAAElI;IACRiI,aAAaC,EAAED;GACjB;GACA,IAAIC,EAAEF,gBAAgBjC,KAAAA,GAAWoC,KAAKH,cAAcE,EAAEF;GACtD,OAAOG;EACT,CAAC,CACH;CACF,CACF;CAEA,MAAMC,YAAYhK,eAChB,OACEiK,QACA1C,cACA2C,YACqB;EACrB,MAAMC,cAAcF,OAAOG,UAAU;EACrC,MAAMV,SACJQ,SAASlC,cAAcL,KAAAA,IACnB,MAAMwC,cACN,MAAMvC,sBACJuC,aACA,iBACAD,QAAQlC,SACV;EACN,IAAI,CAACV,oBAAoBC,YAAU,GAAG,OAAO;EAC7CkC,eAAeC,MAAI;EACnB,OAAO;CACT,CACF;CAEA,MAAMW,kBAAkBrK,eACtB,OACE4F,aACA2B,iBACqB;EACrB,MAAM+C,gBAA+B;GACnC,GAAIzH,MAAMP,gBAAgB,QACtB,CAAC,IACD,EAAEiI,cAAc,EAAEjI,aAAa,CAAC,EAAE,EAAE;GACxCkI,aAAa,EACXrG,OAAO;IACLsG,aAAa;IACbC,YAAY;IACZC,YAAY/D,SAAOgE,UAAU;KAC3B,IAAI,CAACtD,oBAAoBC,YAAU,GAAG;KACtC,IAAIX,YAAU,MAAM;MAClBtC,aAAa,EAAEC,SAASqC,QAAMrC,QAAQ,CAAC;MACvC;KACF;KACA,IAAIqG,UAAU,MAAM;KACpBtG,aAAa,IAAI;KACjBmF,eAAe,EAAEtF,OAAOyG,MAAM,CAAC;IACjC;GACF,EACF;EACF;EACA,IAAI/H,MAAMT,OAAOC,iBAAiBsF,KAAAA,GAChC2C,cAAcO,oBAAoBhI,MAAMT,MAAMC;EAEhD,MAAM4H,WAAS,IAAI5J,OACjB;GACEuB,MAAM;GACNkJ,SAAS;EACX,GACAR,aACF;EACA,IAAIzH,MAAMP,gBAAgB,OACxB2H,SAAOc,kBACL,uBACCC,SAAwBC,YAAmC;GAC1D,IAAI,CAAC3D,oBAAoBC,YAAU,GACjC,OAAO/E,QAAQ0C,QAAQ,EAAE8B,QAAQ,SAAS,CAAC;GAE7C,IAAI,EAAE,qBAAqBgE,QAAQE,SACjC,OAAO1I,QAAQ0C,QAAQ,EAAE8B,QAAQ,SAAS,CAAC;GAE7C,MAAM,EAAEzC,SAASgB,oBAAoByF,QAAQE;GAE7C,MAAMxJ,OAAKF,YAAY;GACvB,MAAMqG,YAAU,IAAIrF,SAAuB0C,YAAY;IACrD,MAAMI,gBAAgB;KACpBe,0BAA0B3E,MAAI,EAAEsF,QAAQ,SAAS,CAAC;IACpD;IACAhC,wBAAwBrC,QAAQwI,IAAIzJ,MAAI;KACtCwD;KACAE,QAAQ6F,QAAQ7F;KAChBE;KACAC;IACF,CAAC;GACH,CAAC;GACDZ,wBAAwBhC,cAAY,CAClC,GAAGA,WACH;IACEjB,IAAAA;IACA6C;IACAgB;GACF,CAAC,CACF;GACD,MAAMe,UAAQtB,wBAAwBrC,QAAQ4D,IAAI7E,IAAE;GACpD,IAAI4E,SAAO;IACT,IAAI2E,QAAQ7F,OAAOgG,SACjB9E,QAAMhB,QAAQ;SAEd2F,QAAQ7F,OAAOiG,iBAAiB,SAAS/E,QAAMhB,SAAS,EACtDgG,MAAM,KACR,CAAC;GAEL;GACA,OAAOzD;EACT,CACF;EAEF,MAAMG,cAAYG,KAAKC,IAAI;EAC3B,MAAMR,sBACJqC,SAAOsB,QAAQ3F,WAAS,GACxB,cACAoC,WACF;EACA,IAAI,CAACV,oBAAoBC,YAAU,GAAG,OAAO;EAM7C,IAAI,CAACiE,MADgBxB,UAAUC,UAAQ1C,cAAY,EAAES,WAAAA,YAAU,CAAC,GACnD,OAAO;EAEpBlD,oBAAoBnC,UAAU;EAC9BiC,UAAUjC,UAAUsH;EACpBpF,aAAalC,UAAUiD;EACvB1B,mBAAmB,WAAW;EAC9B,OAAO;CACT,CACF;CAEA,MAAMuH,YAAYzL,eAAe,YAAY;EAC3C,MAAMuH,eAAa,EAAExC,wBAAwBpC;EAE7C,MAAM0E,gBAAgB;EACtB,IAAI,CAACC,oBAAoBC,YAAU,GAAG;EAEtCrD,mBAAmB,YAAY;EAC/BI,aAAa,IAAI;EACjBG,oBAAoB,IAAI;EAGxBL,SAAS,CAAA,CAAE;EACX,IAAIwB,cAAkD;EACtD,IAAI;GACFA,cAAY,MAAMiD,eAAe;GACjC,IAAI,CAACvB,oBAAoBC,YAAU,GAAG;IACpC,MAAMzB,sBAAsB,CAACF,WAAS,CAAC;IACvC;GACF;GACAd,oBAAoBnC,UAAUiD;GAI9B,MAAMyE,gBAAgBzE,aAAW2B,YAAU;EAC7C,SAASmE,KAAK;GACZ,IAAI,CAACpE,oBAAoBC,YAAU,GAAG;GAEtC,IAAImE,eAAenL,mBAAmB;IAIpCuE,oBAAoBnC,UAAU;IAC9BkC,aAAalC,UAAUiD;IACvB1B,mBAAmB,cAAc;GACnC,OAAO;IACL6C,0BAA0B;IAC1B,IAAInB,aAAW;KACbd,oBAAoBnC,UAAU;KAC9B,MAAMmD,sBAAsB,CAACF,WAAS,CAAC;IACzC;IACAtB,aAAa,EACXC,SAASmH,eAAehE,QAAQgE,IAAInH,UAAUoH,OAAOD,GAAG,EAC1D,CAAC;IACDxH,mBAAmB,OAAO;GAC5B;EACF;CACF,CAAC;CAED,MAAM0H,eAAe5L,eAAe,YAAY;EAC9C+E,wBAAwBpC,WAAW;EACnCyB,SAAS,CAAA,CAAE;EACXK,oBAAoB,IAAI;EACxBP,mBAAmB,cAAc;EACjC,MAAMmD,gBAAgB;CACxB,CAAC;CAED,MAAMwE,iBAAiB7L,eAAe,OAAO8L,gBAAwB;EACnE,MAAMvE,eAAa,EAAExC,wBAAwBpC;EAC7CoE,0BAA0B;EAC1B,MAAMX,sBAAsB;EAC5B,IAAI,CAACkB,oBAAoBC,YAAU,GAAG,MAAMC,2BAA2B;EAEvEtD,mBAAmB,aAAa;EAChCI,aAAa,IAAI;EACjB,IAAI;GAEF,MAAMyH,OAAOlK,IADGsH,IAAI2C,WACPjK,CAAG,CAACmK,aAAazF,IAAI,MAAM;GACxC,IAAI,CAACwF,MAAM,MAAM,IAAIrE,MAAM,4CAA4C;GACvE,IAAI9B,cAAYf,aAAalC;GAC7B,IAAI,CAACiD,aAAW;IACdA,cAAY,MAAMiD,eAAe;IACjC,IAAI,CAACvB,oBAAoBC,YAAU,GAAG;KACpC,MAAMzB,sBAAsB,CAACF,WAAS,CAAC;KACvC,MAAM4B,2BAA2B;IACnC;GACF;GACA3C,aAAalC,UAAU;GACvBiC,UAAUjC,UAAU;GACpBmC,oBAAoBnC,UAAUiD;GAC9B,MAAMA,YAAUqG,WAAWF,IAAI;GAC/B,IAAI,CAACzE,oBAAoBC,YAAU,GAAG,MAAMC,2BAA2B;GACvE/C,oBAAoB,IAAI;GAExB,IAAI,CAACyH,MADmB7B,gBAAgBzE,aAAW2B,YAAU,GAC7C,MAAMC,2BAA2B;EACnD,SAASkE,OAAK;GACZ,MAAM9E,UAAQ8E,iBAAehE,QAAQgE,QAAM,IAAIhE,MAAMiE,OAAOD,KAAG,CAAC;GAChE,IAAI,CAACpE,oBAAoBC,YAAU,GACjC,MAAMC,2BAA2BZ,OAAK;GAExC,MAAMS,gBAAgB;GACtB/C,aAAa,EACXC,SAASqC,QAAMrC,QACjB,CAAC;GACDL,mBAAmB,OAAO;GAC1B,MAAM0C;EACR;CACF,CAAC;CAED,MAAMuF,iBAAiBnM,eACrB,OAAOoF,WAAmC;EACxC,IAAI,CAACvC,MAAMX,aAAa;EACxB,IAAIW,MAAMd,KAAKgB,SAAS,QAAQ;GAC9B,UAAe;GACf;EACF;EAEA,IAAIF,MAAMd,KAAKgB,SAAS,YAAYF,MAAMd,KAAKiB,OAAO;GACpD,UAAe;GACf;EACF;EACA,MAAMuE,eAAaxC,wBAAwBpC;EAC3C,IAAIyG;EACJ,IAAI;GACFA,cAAY,MAAMvG,MAAMb,QAAQqH,cAAcxG,MAAMnB,EAAE;EACxD,SAASkF,SAAO;GACd,IAAIxB,OAAOK,aAAa,CAAC6B,oBAAoBC,YAAU,GAAG;GAC1D,MAAMhD,YAAUqC,mBAAiBc,QAAQd,QAAMrC,UAAUoH,OAAO/E,OAAK;GACrEtC,aAAa,EACXC,SAAS,eAAe1B,MAAMnB,GAAE,yCAA0C6C,YAC5E,CAAC;GACDL,mBAAmB,OAAO;GAC1B;EACF;EACA,IAAIkB,OAAOK,aAAa,CAAC6B,oBAAoBC,YAAU,GAAG;EAC1D,IAAI1E,MAAMd,KAAKgB,SAAS,SAClB;OAAA,CAACqG,aAAWiD,QAAQ;EAAA,OACnB,IAAI,CAACjD,aAAWpG,OACrB;EAEF,UAAe;CACjB,CACF;CAGAlD,gBAAgB;EACd,MAAM4C,yBAAyBG,MAAMH;EACrC,MAAM4J,mBAAmB9G,mBAAmB7C;EAC5C,IAAI2J,kBAAkBA,iBAAiB7G,YAAY;EACnD,MAAM8G,kBAAkB,EAAE9G,WAAW,MAAM;EAC3CD,mBAAmB7C,UAAU4J;EAC7B7G,WAAW/C,UAAU;EACrB,MAAMyC,WAAS,EAAEK,WAAW,MAAM;EAClC,eAAoBL,QAAM;EAC1B,aAAa;GACXM,WAAW/C,UAAU;GACrByC,SAAOK,YAAY;GACnB,MAAMO,SAAOtD,uBAAuBC,QAAQsD,KAAK,YAAY;IAC3D,MAAM,IAAIzD,SAAe0C,cAAYsH,eAAetH,SAAO,CAAC;IAC5D,IAAIqH,gBAAgB9G,WAAW;IAC/BV,wBAAwBpC,WAAW;IACnC,MAAMH,QAAQ0D,IAAIe,iBAAiB,CAAC,CAACd,IAAIR,oBAAoB,CAAC;GAChE,CAAC;GACDjD,uBAAuBC,UAAUqD;EACnC;CACF,GAAG,CAACnD,MAAMH,sBAAsB,CAAC;CAEjC,MAAM+J,QAAQ1M,eACL;EACL2B,IAAImB,MAAMnB;EACVC,MAAMkB,MAAMlB;EACZC,MAAMiB,MAAMjB;EACZC,KAAKgB,MAAMhB;EACXC,MAAMe,MAAMf;EACZmC;EACAI;EACAF;EACAK;EACAE;CACF,IACA;EACE7B,MAAMnB;EACNmB,MAAMlB;EACNkB,MAAMjB;EACNiB,MAAMhB;EACNgB,MAAMf;EACNmC;EACAI;EACAF;EACAK;EACAE;CAAmB,CAEvB;CAEA,OAAO;EACLgI,gBAAgBD;EAChBlB,SAASE;EACTkB,YAAYf;EACZgB,QAAQ,YAAY;GAClB,MAAMhB,aAAa;GACnB,IAAI;IACF,MAAM/I,MAAMb,QAAQ6K,eAAehK,MAAMnB,EAAE;IAC3C,MAAMmB,MAAMN,SAAS;GACvB,SAASmJ,OAAK;IACZpH,aAAa,EACXC,SAASmH,iBAAehE,QAAQgE,MAAInH,UAAUoH,OAAOD,KAAG,EAC1D,CAAC;IACD,MAAMA;GACR;EACF;EACAoB,UAAU,OAAOlL,MAAMmL,SAAS;GAC9B,MAAM9C,WAASrF,UAAUjC;GACzB,IAAI,CAACsH,UACH,MAAM,IAAIvC,MAAM,eAAe7E,MAAMnB,GAAE,mBAAoB;GAE7D,OAAO,MAAMuI,SAAO6C,SAAS;IAC3BlL;IACAoL,WAAWD;GACb,CAAC;EACH;EACAG,eAAe,OAAOhC,WAAW;GAC/B,MAAMjB,WAASrF,UAAUjC;GACzB,IAAI,CAACsH,UACH,MAAM,IAAIvC,MAAM,eAAe7E,MAAMnB,GAAE,mBAAoB;GAE7D,OAAO,MAAMuI,SAAOiD,cAAchC,MAAM;EAC1C;EACAiC,cAAc,OAAOC,QAAQ;GAC3B,MAAMnD,WAASrF,UAAUjC;GACzB,IAAI,CAACsH,UACH,MAAM,IAAIvC,MAAM,eAAe7E,MAAMnB,GAAE,mBAAoB;GAE7D,OAAO,MAAMuI,SAAOkD,aAAa,EAAEC,IAAI,CAAC;EAC1C;EACAC,cAAcxB;EACdyB,oBACE5L,MACA6L,aACiE;GACjE,IAAIA,SAASvG,WAAW,UAAU;IAChC,MAAMV,UAAQtB,wBAAwBrC,QAAQ4D,IAAI7E,IAAE;IACpD,IAAI,CAAC4E,SAAO;IAEZ,IACE,OAAOiH,SAASE,YAAY,YAC5BF,SAASE,YAAY,QACrB9D,MAAM+D,QAAQH,SAASE,OAAO,GAC9B;KACA,MAAME,SAAS,CACb;MACEH,UAAU;MACVjJ,SAAS;KACX,CAAC;KAEHoC,2BAA2BjF,MAAI;MAC7B6C,SAAS;MACTqJ,YAAY,CAAC,SAAS;KACxB,CAAC;KACD,OAAOD;IACT;IAEA,MAAMA,WAAS5M,2BACbuF,QAAMf,iBACNgI,SAASE,OACX;IACA,IAAIE,SAAOzK,SAAS,GAAG;KACrB,MAAM0K,aAAa,CACjB,GAAG,IAAIxG,IAAIuG,SAAOxH,KAAKS,YAAUA,QAAM4G,QAAQ,CAAC,CAAC;KAEnD7G,2BAA2BjF,MAAI;MAC7B6C,SAAS,gCAAgCqJ,WAAWC,KAAK,IAAI,EAAC;MAC9DD;KACF,CAAC;KACD,OAAOD;IACT;IAEA,MAAMxI,WAAuB;KAC3B6B,QAAQ;KACRyG,SAASF,SAASE;IACpB;IACApH,0BAA0B3E,MAAIyD,QAAM;IACpC;GACF;GAEAkB,0BAA0B3E,MAAI,EAAEsF,QAAQuG,SAASvG,OAAO,CAAC;EAC3D;CACF;AACF;AAEA,MAAM8G,4BAA4B7N,SAAS+D,4BAA4B;AAEvE,MAAa+J,oBAAoB9N,SAAS,SAAS+N,qBACjDnL,OAC2B;CAC3B,MAAMH,yBAAyB7C,OAAO2C,QAAQ0C,QAAQ,CAAC;CACvD,MAAM+I,eAAerL,0BAA0BC,KAAK;CACpD,MAAM,CAACqL,YAAYC,iBAAiBvO,SAAS;EAAEqO;EAAc1G,YAAY;CAAE,CAAC;CAC5E,IAAI6G,oBAAoBF;CACxB,IAAI,CAAC1K,+BAA+B0K,WAAWD,cAAcA,YAAY,GAAG;EAC1EG,oBAAoB;GAClBH;GACA1G,YAAY2G,WAAW3G,aAAa;EACtC;EACA4G,cAAcC,iBAAiB;CACjC;CAIA,OAAOlO,YACLC,QACEiO,kBAAkB7G,YAClBuG,0BAA0B;EAAE,GAAGjL;EAAOH;CAAuB,CAAC,CAChE,CACF;AACF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/utils/createMcpId.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Both forms are alphanumeric or hyphenated, so neither can contain the
|
|
4
|
+
* separators `assertValidServerId` reserves.
|
|
5
|
+
*/
|
|
6
|
+
declare const createMcpId: () => string;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { createMcpId };
|
|
9
|
+
//# sourceMappingURL=createMcpId.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createMcpId.d.ts","names":[],"sources":["../../src/utils/createMcpId.ts"],"mappings":";;;;;cAMa"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { generateId } from "@assistant-ui/core";
|
|
2
|
+
//#region src/utils/createMcpId.ts
|
|
3
|
+
/**
|
|
4
|
+
* Both forms are alphanumeric or hyphenated, so neither can contain the
|
|
5
|
+
* separators `assertValidServerId` reserves.
|
|
6
|
+
*/
|
|
7
|
+
const createMcpId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `mcp-${generateId()}`;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { createMcpId };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=createMcpId.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createMcpId.js","names":["generateId","createMcpId","crypto","randomUUID"],"sources":["../../src/utils/createMcpId.ts"],"sourcesContent":["import { generateId } from \"@assistant-ui/core\";\n\n/**\n * Both forms are alphanumeric or hyphenated, so neither can contain the\n * separators `assertValidServerId` reserves.\n */\nexport const createMcpId = (): string =>\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : `mcp-${generateId()}`;\n"],"mappings":";;;;;;AAMA,MAAaC,oBACX,OAAOC,WAAW,eAAe,gBAAgBA,SAC7CA,OAAOC,WAAW,IAClB,OAAOH,WAAW"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invokeMcpCallback.d.ts","names":[],"sources":["../../src/utils/invokeMcpCallback.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"invokeMcpCallback.d.ts","names":[],"sources":["../../src/utils/invokeMcpCallback.ts"],"mappings":";cAEa,oBAAqB,yBAChC,cACA,eAAe,MAAM,gCAClB,MAAM"}
|
|
@@ -1,17 +1,7 @@
|
|
|
1
|
+
import { invokeUserCallback } from "@assistant-ui/core/internal";
|
|
1
2
|
//#region src/utils/invokeMcpCallback.ts
|
|
2
|
-
const reportCallbackError = (name, error) => {
|
|
3
|
-
console.error(`[react-mcp] ${name} callback threw an error`, error);
|
|
4
|
-
};
|
|
5
3
|
const invokeMcpCallback = (name, callback, ...args) => {
|
|
6
|
-
|
|
7
|
-
try {
|
|
8
|
-
const result = callback(...args);
|
|
9
|
-
if (result !== null && (typeof result === "object" || typeof result === "function") && "then" in result && typeof result.then === "function") Promise.resolve(result).catch((error) => {
|
|
10
|
-
reportCallbackError(name, error);
|
|
11
|
-
});
|
|
12
|
-
} catch (error) {
|
|
13
|
-
reportCallbackError(name, error);
|
|
14
|
-
}
|
|
4
|
+
invokeUserCallback("react-mcp", name, callback, ...args);
|
|
15
5
|
};
|
|
16
6
|
//#endregion
|
|
17
7
|
export { invokeMcpCallback };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invokeMcpCallback.js","names":["
|
|
1
|
+
{"version":3,"file":"invokeMcpCallback.js","names":["invokeUserCallback","invokeMcpCallback","name","callback","args","TArgs"],"sources":["../../src/utils/invokeMcpCallback.ts"],"sourcesContent":["import { invokeUserCallback } from \"@assistant-ui/core/internal\";\n\nexport const invokeMcpCallback = <TArgs extends unknown[]>(\n name: string,\n callback: ((...args: TArgs) => void) | undefined,\n ...args: TArgs\n): void => {\n void invokeUserCallback(\"react-mcp\", name, callback, ...args);\n};\n"],"mappings":";;AAEA,MAAaC,qBACXC,MACAC,UACA,GAAGC,SACM;CACT,mBAAwB,aAAaF,MAAMC,UAAU,GAAGC,IAAI;AAC9D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
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.16",
|
|
31
|
+
"@assistant-ui/store": "0.3.11",
|
|
32
|
+
"@assistant-ui/tap": "0.9.15",
|
|
30
33
|
"@modelcontextprotocol/client": "^2.0.0",
|
|
31
34
|
"@modelcontextprotocol/core": "^2.0.0",
|
|
32
35
|
"@radix-ui/react-primitive": "^2.1.10",
|
|
33
|
-
"
|
|
34
|
-
"@assistant-ui/store": "0.3.10",
|
|
35
|
-
"@assistant-ui/tap": "0.9.13",
|
|
36
|
-
"assistant-stream": "0.3.38"
|
|
36
|
+
"assistant-stream": "0.3.40"
|
|
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.24",
|
|
48
49
|
"@testing-library/dom": "^10.4.1",
|
|
49
50
|
"@testing-library/react": "^16.3.2",
|
|
50
51
|
"@types/react": "^19.2.18",
|
|
51
52
|
"jsdom": "^30.0.1",
|
|
52
53
|
"react": "^19.2.8",
|
|
53
|
-
"vitest": "^4.1.
|
|
54
|
-
"@assistant-ui/x-buildutils": "0.0.23"
|
|
54
|
+
"vitest": "^4.1.11"
|
|
55
55
|
},
|
|
56
56
|
"publishConfig": {
|
|
57
57
|
"access": "public",
|
|
@@ -147,6 +147,134 @@ describe("McpManagerResource server ids", () => {
|
|
|
147
147
|
root.unmount();
|
|
148
148
|
}
|
|
149
149
|
});
|
|
150
|
+
|
|
151
|
+
it("replaces a connected transport when connector settings change", async () => {
|
|
152
|
+
mocks.StreamableHTTPClientTransport.mockClear();
|
|
153
|
+
let updateConnector = (_connector: MCPConnector) => {};
|
|
154
|
+
const DynamicManager = resource(function useDynamicManager() {
|
|
155
|
+
const [currentConnector, setCurrentConnector] = useState(
|
|
156
|
+
connector("docs"),
|
|
157
|
+
);
|
|
158
|
+
updateConnector = setCurrentConnector;
|
|
159
|
+
|
|
160
|
+
return useResource(
|
|
161
|
+
McpManagerResource({
|
|
162
|
+
connectors: [currentConnector],
|
|
163
|
+
storage: McpMemoryStorage(),
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
const root = createTapRoot(function Root() {
|
|
168
|
+
return useResource(DynamicManager());
|
|
169
|
+
});
|
|
170
|
+
let resolveFirstClose = () => {};
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
await vi.waitFor(() =>
|
|
174
|
+
expect(mocks.StreamableHTTPClientTransport).toHaveBeenCalledOnce(),
|
|
175
|
+
);
|
|
176
|
+
const firstTransport = mocks.StreamableHTTPClientTransport.mock
|
|
177
|
+
.instances[0] as { close: ReturnType<typeof vi.fn> };
|
|
178
|
+
firstTransport.close.mockImplementation(
|
|
179
|
+
() =>
|
|
180
|
+
new Promise<void>((resolve) => {
|
|
181
|
+
resolveFirstClose = resolve;
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
updateConnector(
|
|
186
|
+
defineConnector({
|
|
187
|
+
id: "docs",
|
|
188
|
+
name: "Docs",
|
|
189
|
+
url: "https://other.example.com/docs/mcp",
|
|
190
|
+
auth: { type: "none" },
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
await vi.waitFor(() =>
|
|
195
|
+
expect(firstTransport.close).toHaveBeenCalledOnce(),
|
|
196
|
+
);
|
|
197
|
+
expect(mocks.StreamableHTTPClientTransport).toHaveBeenCalledOnce();
|
|
198
|
+
|
|
199
|
+
resolveFirstClose();
|
|
200
|
+
await vi.waitFor(() =>
|
|
201
|
+
expect(mocks.StreamableHTTPClientTransport).toHaveBeenCalledTimes(2),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
expect(mocks.StreamableHTTPClientTransport).toHaveBeenLastCalledWith(
|
|
205
|
+
new URL("https://other.example.com/docs/mcp"),
|
|
206
|
+
);
|
|
207
|
+
} finally {
|
|
208
|
+
resolveFirstClose();
|
|
209
|
+
root.unmount();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("keeps a connection across equivalent and cosmetic connector updates", async () => {
|
|
214
|
+
mocks.StreamableHTTPClientTransport.mockClear();
|
|
215
|
+
let rerenderEquivalent = () => {};
|
|
216
|
+
let updatePresentation = () => {};
|
|
217
|
+
const DynamicManager = resource(function useDynamicManager() {
|
|
218
|
+
const [, setVersion] = useState(0);
|
|
219
|
+
const [presentation, setPresentation] = useState({
|
|
220
|
+
name: "Docs",
|
|
221
|
+
icon: "docs.svg",
|
|
222
|
+
});
|
|
223
|
+
rerenderEquivalent = () => setVersion((version) => version + 1);
|
|
224
|
+
updatePresentation = () =>
|
|
225
|
+
setPresentation({ name: "Documentation", icon: "book.svg" });
|
|
226
|
+
|
|
227
|
+
return useResource(
|
|
228
|
+
McpManagerResource({
|
|
229
|
+
connectors: [
|
|
230
|
+
defineConnector({
|
|
231
|
+
id: "docs",
|
|
232
|
+
name: presentation.name,
|
|
233
|
+
icon: presentation.icon,
|
|
234
|
+
url: "https://example.com/docs/mcp",
|
|
235
|
+
auth: { type: "none" },
|
|
236
|
+
}),
|
|
237
|
+
],
|
|
238
|
+
storage: McpCustomStorage({
|
|
239
|
+
loadCustomServers: vi.fn(async () => []),
|
|
240
|
+
saveCustomServers: vi.fn(async () => {}),
|
|
241
|
+
loadAuthState: vi.fn(async () => null),
|
|
242
|
+
saveAuthState: vi.fn(async () => {}),
|
|
243
|
+
clearAuthState: vi.fn(async () => {}),
|
|
244
|
+
}),
|
|
245
|
+
autoConnect: false,
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
const root = createTapRoot(function Root() {
|
|
250
|
+
return useResource(DynamicManager());
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
await root.getValue().connector({ index: 0 }).connect();
|
|
255
|
+
const transport = mocks.StreamableHTTPClientTransport.mock
|
|
256
|
+
.instances[0] as { close: ReturnType<typeof vi.fn> };
|
|
257
|
+
|
|
258
|
+
rerenderEquivalent();
|
|
259
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
260
|
+
expect(mocks.StreamableHTTPClientTransport).toHaveBeenCalledOnce();
|
|
261
|
+
expect(transport.close).not.toHaveBeenCalled();
|
|
262
|
+
|
|
263
|
+
updatePresentation();
|
|
264
|
+
await vi.waitFor(() =>
|
|
265
|
+
expect(
|
|
266
|
+
root.getValue().connector({ index: 0 }).getState(),
|
|
267
|
+
).toMatchObject({
|
|
268
|
+
name: "Documentation",
|
|
269
|
+
icon: "book.svg",
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
272
|
+
expect(mocks.StreamableHTTPClientTransport).toHaveBeenCalledOnce();
|
|
273
|
+
expect(transport.close).not.toHaveBeenCalled();
|
|
274
|
+
} finally {
|
|
275
|
+
root.unmount();
|
|
276
|
+
}
|
|
277
|
+
});
|
|
150
278
|
});
|
|
151
279
|
|
|
152
280
|
describe("McpManagerResource storage failures", () => {
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from "@assistant-ui/store";
|
|
9
9
|
import { useAssistantScopeEffect } from "@assistant-ui/store/client";
|
|
10
10
|
import { ModelContext } from "@assistant-ui/core/store";
|
|
11
|
+
import { createMcpId } from "../utils/createMcpId";
|
|
11
12
|
import type { Tool } from "assistant-stream";
|
|
12
13
|
import { McpServerResource } from "./McpServerResource";
|
|
13
14
|
import { McpLocalStorage } from "./storage/McpLocalStorage";
|
|
@@ -275,10 +276,7 @@ const useMcpManagerResource = (
|
|
|
275
276
|
elicitation,
|
|
276
277
|
}) => {
|
|
277
278
|
const record: MCPCustomServerRecord = {
|
|
278
|
-
id:
|
|
279
|
-
typeof crypto !== "undefined" && "randomUUID" in crypto
|
|
280
|
-
? crypto.randomUUID()
|
|
281
|
-
: `mcp-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
279
|
+
id: createMcpId(),
|
|
282
280
|
name,
|
|
283
281
|
url,
|
|
284
282
|
auth: auth as MCPAuthConfig,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createTapRoot, useResource } from "@assistant-ui/tap";
|
|
1
|
+
import { createTapRoot, resource, useResource } from "@assistant-ui/tap";
|
|
2
2
|
import type { ClientOutput } from "@assistant-ui/store";
|
|
3
|
-
import { useEffect } from "react";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
import type { MCPAuthConfig } from "../mcp-scope";
|
|
6
6
|
import type { MCPStorage } from "./storage/types";
|
|
@@ -444,6 +444,47 @@ describe("McpServerResource connectionTimeout", () => {
|
|
|
444
444
|
describe("McpServerResource connection lifecycle", () => {
|
|
445
445
|
beforeEach(resetMocks);
|
|
446
446
|
|
|
447
|
+
it("replaces direct resource connections when the server id changes", async () => {
|
|
448
|
+
const storage = createStorage();
|
|
449
|
+
let updateId = (_id: string) => {};
|
|
450
|
+
const DynamicServer = resource(function useDynamicServer() {
|
|
451
|
+
const [id, setId] = useState("docs");
|
|
452
|
+
updateId = setId;
|
|
453
|
+
return useResource(
|
|
454
|
+
McpServerResource({
|
|
455
|
+
id,
|
|
456
|
+
kind: "connector",
|
|
457
|
+
name: "Docs",
|
|
458
|
+
url: "https://example.com/mcp",
|
|
459
|
+
auth: { type: "none" },
|
|
460
|
+
storage,
|
|
461
|
+
redirectUri: "https://example.com/callback",
|
|
462
|
+
autoConnect: true,
|
|
463
|
+
onRemove: vi.fn(async () => {}),
|
|
464
|
+
}),
|
|
465
|
+
);
|
|
466
|
+
});
|
|
467
|
+
const root = createTapRoot(function Root() {
|
|
468
|
+
return useResource(DynamicServer());
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
await waitForResourceUpdate(() => mocks.transports.length === 1);
|
|
473
|
+
const firstTransport = mocks.transports[0];
|
|
474
|
+
|
|
475
|
+
updateId("internal-docs");
|
|
476
|
+
|
|
477
|
+
await waitForResourceUpdate(
|
|
478
|
+
() =>
|
|
479
|
+
firstTransport.close.mock.calls.length === 1 &&
|
|
480
|
+
mocks.transports.length === 2,
|
|
481
|
+
);
|
|
482
|
+
expect(root.getValue().getState().id).toBe("internal-docs");
|
|
483
|
+
} finally {
|
|
484
|
+
root.unmount();
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
|
|
447
488
|
it("closes a pending connection when the resource unmounts", async () => {
|
|
448
489
|
let resolveConnect!: () => void;
|
|
449
490
|
mocks.connectResults.push(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useRef, useEffect, useMemo, useEffectEvent } from "react";
|
|
2
|
-
import { resource } from "@assistant-ui/tap";
|
|
2
|
+
import { resource, useResource, withKey } from "@assistant-ui/tap";
|
|
3
3
|
import type { ClientOutput } from "@assistant-ui/store";
|
|
4
4
|
import {
|
|
5
5
|
Client,
|
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
MCPServerState,
|
|
25
25
|
MCPToolInfo,
|
|
26
26
|
} from "../mcp-scope";
|
|
27
|
+
import { createMcpId } from "../utils/createMcpId";
|
|
27
28
|
|
|
28
29
|
export type McpServerResourceProps = {
|
|
29
30
|
id: string;
|
|
@@ -41,8 +42,49 @@ export type McpServerResourceProps = {
|
|
|
41
42
|
onRemove: () => Promise<void>;
|
|
42
43
|
};
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
type McpServerResourceInstanceProps = McpServerResourceProps & {
|
|
46
|
+
transportCloseQueueRef: { current: Promise<void> };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const getConnectionDependencies = (
|
|
45
50
|
props: McpServerResourceProps,
|
|
51
|
+
): readonly unknown[] => {
|
|
52
|
+
const auth = props.auth;
|
|
53
|
+
const authDependencies =
|
|
54
|
+
auth.type === "bearer"
|
|
55
|
+
? [auth.type, auth.token]
|
|
56
|
+
: auth.type === "oauth"
|
|
57
|
+
? [
|
|
58
|
+
auth.type,
|
|
59
|
+
auth.scopes?.length,
|
|
60
|
+
...(auth.scopes ?? []),
|
|
61
|
+
auth.authorizationEndpoint,
|
|
62
|
+
auth.tokenEndpoint,
|
|
63
|
+
auth.registrationEndpoint,
|
|
64
|
+
auth.clientId,
|
|
65
|
+
auth.clientSecret,
|
|
66
|
+
]
|
|
67
|
+
: [auth.type];
|
|
68
|
+
|
|
69
|
+
return [
|
|
70
|
+
props.id,
|
|
71
|
+
props.url,
|
|
72
|
+
...authDependencies,
|
|
73
|
+
props.redirectUri,
|
|
74
|
+
props.cache?.defaultTtlMs,
|
|
75
|
+
props.elicitation !== false,
|
|
76
|
+
];
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const areConnectionDependenciesEqual = (
|
|
80
|
+
left: readonly unknown[],
|
|
81
|
+
right: readonly unknown[],
|
|
82
|
+
) =>
|
|
83
|
+
left.length === right.length &&
|
|
84
|
+
left.every((value, index) => Object.is(value, right[index]));
|
|
85
|
+
|
|
86
|
+
const useMcpServerResourceInstance = (
|
|
87
|
+
props: McpServerResourceInstanceProps,
|
|
46
88
|
): ClientOutput<"mcpServer"> => {
|
|
47
89
|
assertValidServerId(props.id);
|
|
48
90
|
const [connectionState, setConnectionState] =
|
|
@@ -59,7 +101,6 @@ const useMcpServerResource = (
|
|
|
59
101
|
const pendingTransportRef = useRef<StreamableHTTPClientTransport | null>(
|
|
60
102
|
null,
|
|
61
103
|
);
|
|
62
|
-
const transportCloseQueueRef = useRef(Promise.resolve());
|
|
63
104
|
const connectionGenerationRef = useRef(0);
|
|
64
105
|
const elicitationResolversRef = useRef(
|
|
65
106
|
new Map<
|
|
@@ -88,10 +129,10 @@ const useMcpServerResource = (
|
|
|
88
129
|
const closeQueuedTransports = (
|
|
89
130
|
transports: StreamableHTTPClientTransport[],
|
|
90
131
|
): Promise<void> => {
|
|
91
|
-
const task = transportCloseQueueRef.current.then(async () => {
|
|
132
|
+
const task = props.transportCloseQueueRef.current.then(async () => {
|
|
92
133
|
await Promise.all(transports.map(closeTransportSafely));
|
|
93
134
|
});
|
|
94
|
-
transportCloseQueueRef.current = task;
|
|
135
|
+
props.transportCloseQueueRef.current = task;
|
|
95
136
|
return task;
|
|
96
137
|
};
|
|
97
138
|
|
|
@@ -132,7 +173,7 @@ const useMcpServerResource = (
|
|
|
132
173
|
}
|
|
133
174
|
};
|
|
134
175
|
|
|
135
|
-
const
|
|
176
|
+
const detachTransports = () => {
|
|
136
177
|
cancelPendingElicitations();
|
|
137
178
|
const pendingTransport = pendingTransportRef.current;
|
|
138
179
|
const activeTransport = transportRef.current;
|
|
@@ -140,13 +181,18 @@ const useMcpServerResource = (
|
|
|
140
181
|
transportRef.current = null;
|
|
141
182
|
clientRef.current = null;
|
|
142
183
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
(
|
|
146
|
-
transport
|
|
184
|
+
return [
|
|
185
|
+
...new Set(
|
|
186
|
+
[pendingTransport, activeTransport].filter(
|
|
187
|
+
(transport): transport is StreamableHTTPClientTransport =>
|
|
188
|
+
transport !== null,
|
|
189
|
+
),
|
|
147
190
|
),
|
|
148
|
-
|
|
149
|
-
|
|
191
|
+
];
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const closeTransports = async () => {
|
|
195
|
+
await closeQueuedTransports(detachTransports());
|
|
150
196
|
};
|
|
151
197
|
|
|
152
198
|
const isCurrentConnection = (generation: number) =>
|
|
@@ -305,10 +351,7 @@ const useMcpServerResource = (
|
|
|
305
351
|
}
|
|
306
352
|
const { message, requestedSchema } = request.params;
|
|
307
353
|
|
|
308
|
-
const id =
|
|
309
|
-
typeof crypto !== "undefined" && "randomUUID" in crypto
|
|
310
|
-
? crypto.randomUUID()
|
|
311
|
-
: `mcp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
354
|
+
const id = createMcpId();
|
|
312
355
|
const promise = new Promise<ElicitResult>((resolve) => {
|
|
313
356
|
const onAbort = () => {
|
|
314
357
|
resolvePendingElicitation(id, { action: "cancel" });
|
|
@@ -499,6 +542,7 @@ const useMcpServerResource = (
|
|
|
499
542
|
|
|
500
543
|
// Auto-connect on mount when usable auth exists.
|
|
501
544
|
useEffect(() => {
|
|
545
|
+
const transportCloseQueueRef = props.transportCloseQueueRef;
|
|
502
546
|
const previousDisposal = pendingDisposalRef.current;
|
|
503
547
|
if (previousDisposal) previousDisposal.cancelled = true;
|
|
504
548
|
const pendingDisposal = { cancelled: false };
|
|
@@ -509,14 +553,15 @@ const useMcpServerResource = (
|
|
|
509
553
|
return () => {
|
|
510
554
|
mountedRef.current = false;
|
|
511
555
|
signal.cancelled = true;
|
|
512
|
-
|
|
513
|
-
|
|
556
|
+
const task = transportCloseQueueRef.current.then(async () => {
|
|
557
|
+
await new Promise<void>((resolve) => queueMicrotask(resolve));
|
|
514
558
|
if (pendingDisposal.cancelled) return;
|
|
515
559
|
connectionGenerationRef.current += 1;
|
|
516
|
-
|
|
560
|
+
await Promise.all(detachTransports().map(closeTransportSafely));
|
|
517
561
|
});
|
|
562
|
+
transportCloseQueueRef.current = task;
|
|
518
563
|
};
|
|
519
|
-
}, []);
|
|
564
|
+
}, [props.transportCloseQueueRef]);
|
|
520
565
|
|
|
521
566
|
const state = useMemo<MCPServerState>(
|
|
522
567
|
() => ({
|
|
@@ -640,4 +685,29 @@ const useMcpServerResource = (
|
|
|
640
685
|
};
|
|
641
686
|
};
|
|
642
687
|
|
|
643
|
-
|
|
688
|
+
const McpServerResourceInstance = resource(useMcpServerResourceInstance);
|
|
689
|
+
|
|
690
|
+
export const McpServerResource = resource(function useMcpServerResource(
|
|
691
|
+
props: McpServerResourceProps,
|
|
692
|
+
): ClientOutput<"mcpServer"> {
|
|
693
|
+
const transportCloseQueueRef = useRef(Promise.resolve());
|
|
694
|
+
const dependencies = getConnectionDependencies(props);
|
|
695
|
+
const [connection, setConnection] = useState({ dependencies, generation: 0 });
|
|
696
|
+
let currentConnection = connection;
|
|
697
|
+
if (!areConnectionDependenciesEqual(connection.dependencies, dependencies)) {
|
|
698
|
+
currentConnection = {
|
|
699
|
+
dependencies,
|
|
700
|
+
generation: connection.generation + 1,
|
|
701
|
+
};
|
|
702
|
+
setConnection(currentConnection);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Storage resources do not expose a stable scope identity and may return a
|
|
706
|
+
// fresh client on ordinary renders, so storage changes cannot key remounts.
|
|
707
|
+
return useResource(
|
|
708
|
+
withKey(
|
|
709
|
+
currentConnection.generation,
|
|
710
|
+
McpServerResourceInstance({ ...props, transportCloseQueueRef }),
|
|
711
|
+
),
|
|
712
|
+
);
|
|
713
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { createMcpId } from "./createMcpId";
|
|
3
|
+
import { assertValidServerId } from "./serverId";
|
|
4
|
+
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
vi.unstubAllGlobals();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe("createMcpId", () => {
|
|
10
|
+
it("uses the platform UUID when one is available", () => {
|
|
11
|
+
vi.stubGlobal("crypto", {
|
|
12
|
+
randomUUID: () => "11111111-2222-3333-4444-555555555555",
|
|
13
|
+
});
|
|
14
|
+
expect(createMcpId()).toBe("11111111-2222-3333-4444-555555555555");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("falls back to a prefixed generated id without randomUUID", () => {
|
|
18
|
+
vi.stubGlobal("crypto", {});
|
|
19
|
+
const first = createMcpId();
|
|
20
|
+
const second = createMcpId();
|
|
21
|
+
expect(first).toMatch(/^mcp-[0-9A-Za-z]+$/);
|
|
22
|
+
expect(second).not.toBe(first);
|
|
23
|
+
expect(() => assertValidServerId(first)).not.toThrow();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { generateId } from "@assistant-ui/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Both forms are alphanumeric or hyphenated, so neither can contain the
|
|
5
|
+
* separators `assertValidServerId` reserves.
|
|
6
|
+
*/
|
|
7
|
+
export const createMcpId = (): string =>
|
|
8
|
+
typeof crypto !== "undefined" && "randomUUID" in crypto
|
|
9
|
+
? crypto.randomUUID()
|
|
10
|
+
: `mcp-${generateId()}`;
|
|
@@ -1,27 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
console.error(`[react-mcp] ${name} callback threw an error`, error);
|
|
3
|
-
};
|
|
1
|
+
import { invokeUserCallback } from "@assistant-ui/core/internal";
|
|
4
2
|
|
|
5
3
|
export const invokeMcpCallback = <TArgs extends unknown[]>(
|
|
6
4
|
name: string,
|
|
7
5
|
callback: ((...args: TArgs) => void) | undefined,
|
|
8
6
|
...args: TArgs
|
|
9
|
-
) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
const result = callback(...args) as unknown;
|
|
14
|
-
if (
|
|
15
|
-
result !== null &&
|
|
16
|
-
(typeof result === "object" || typeof result === "function") &&
|
|
17
|
-
"then" in result &&
|
|
18
|
-
typeof result.then === "function"
|
|
19
|
-
) {
|
|
20
|
-
void Promise.resolve(result).catch((error) => {
|
|
21
|
-
reportCallbackError(name, error);
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
} catch (error) {
|
|
25
|
-
reportCallbackError(name, error);
|
|
26
|
-
}
|
|
7
|
+
): void => {
|
|
8
|
+
void invokeUserCallback("react-mcp", name, callback, ...args);
|
|
27
9
|
};
|