@assistant-ui/react-mcp 0.1.4 → 0.1.6
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMcpOAuthCallback.d.ts","names":[],"sources":["../../src/hooks/useMcpOAuthCallback.tsx"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"useMcpOAuthCallback.d.ts","names":[],"sources":["../../src/hooks/useMcpOAuthCallback.tsx"],"mappings":";;cAkCa,8BACX,cACA,4BACC;KAWS;;EAEV;EACA,cAAc;EACd,WAAW,KAAK;;KAGN;EACV;EACA;EACA,OAAO;;iBAGO,oBACd,OAAM,6BACL;cA0DU,kBAAkB,GAC7B;EACE,YAAY,QAAQ,8BAA8B"}
|
|
@@ -4,6 +4,20 @@ import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
|
4
4
|
import { useEffect, useRef, useState } from "@assistant-ui/tap/react-shim";
|
|
5
5
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
6
6
|
//#region src/hooks/useMcpOAuthCallback.tsx
|
|
7
|
+
const reportCallbackError = (name, error) => {
|
|
8
|
+
console.error(`[react-mcp] ${name} callback threw an error`, error);
|
|
9
|
+
};
|
|
10
|
+
const invokeMcpOAuthCallback = (name, callback, ...args) => {
|
|
11
|
+
if (!callback) return;
|
|
12
|
+
try {
|
|
13
|
+
const result = callback(...args);
|
|
14
|
+
if (result !== null && (typeof result === "object" || typeof result === "function") && "then" in result && typeof result.then === "function") Promise.resolve(result).catch((error) => {
|
|
15
|
+
reportCallbackError(name, error);
|
|
16
|
+
});
|
|
17
|
+
} catch (error) {
|
|
18
|
+
reportCallbackError(name, error);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
7
21
|
const createMcpOAuthCallbackError = (err, serverId) => {
|
|
8
22
|
const message = err instanceof Error ? err.message : String(err);
|
|
9
23
|
if (serverId) return new Error(`MCP OAuth callback for server "${serverId}" failed: ${message}`, { cause: err });
|
|
@@ -30,8 +44,8 @@ function useMcpOAuthCallback(opts = {}) {
|
|
|
30
44
|
const parsed = new URL(url);
|
|
31
45
|
const state = parsed.searchParams.get("state");
|
|
32
46
|
if (state) serverId = decodeServerIdFromState(state);
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
47
|
+
const error_0 = parsed.searchParams.get("error");
|
|
48
|
+
if (error_0) throw new Error(parsed.searchParams.get("error_description") ?? error_0);
|
|
35
49
|
if (!state) throw new Error("missing \"state\" parameter");
|
|
36
50
|
if (!serverId) throw new Error("state was not created by assistant-ui MCP");
|
|
37
51
|
setResult({
|
|
@@ -45,16 +59,17 @@ function useMcpOAuthCallback(opts = {}) {
|
|
|
45
59
|
serverId,
|
|
46
60
|
error: null
|
|
47
61
|
});
|
|
48
|
-
optsRef.current.onComplete?.(serverId);
|
|
49
62
|
} catch (err) {
|
|
50
|
-
const
|
|
63
|
+
const error = createMcpOAuthCallbackError(err, serverId);
|
|
51
64
|
setResult({
|
|
52
65
|
status: "error",
|
|
53
66
|
serverId,
|
|
54
|
-
error
|
|
67
|
+
error
|
|
55
68
|
});
|
|
56
|
-
optsRef.current.onError
|
|
69
|
+
invokeMcpOAuthCallback("onError", optsRef.current.onError, error);
|
|
70
|
+
return;
|
|
57
71
|
}
|
|
72
|
+
invokeMcpOAuthCallback("onComplete", optsRef.current.onComplete, serverId);
|
|
58
73
|
})();
|
|
59
74
|
}, [aui, opts.url]);
|
|
60
75
|
return result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMcpOAuthCallback.js","names":["FC","ReactNode","useEffect","useRef","useState","useAui","decodeServerIdFromState","createMcpOAuthCallbackError","err","serverId","Error","message","String","cause","UseMcpOAuthCallbackOptions","url","onComplete","onError","UseMcpOAuthCallbackResult","status","
|
|
1
|
+
{"version":3,"file":"useMcpOAuthCallback.js","names":["FC","ReactNode","useEffect","useRef","useState","useAui","decodeServerIdFromState","McpOAuthCallbackName","reportCallbackError","name","error","console","invokeMcpOAuthCallback","callback","args","TArgs","result","then","Promise","resolve","catch","createMcpOAuthCallbackError","err","serverId","Error","message","String","cause","UseMcpOAuthCallbackOptions","url","onComplete","onError","UseMcpOAuthCallbackResult","status","useMcpOAuthCallback","opts","aui","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\";\n\ntype McpOAuthCallbackName = \"onComplete\" | \"onError\";\n\nconst reportCallbackError = (name: McpOAuthCallbackName, error: unknown) => {\n console.error(`[react-mcp] ${name} callback threw an error`, error);\n};\n\nconst invokeMcpOAuthCallback = <TArgs extends unknown[]>(\n name: McpOAuthCallbackName,\n callback: ((...args: TArgs) => void) | undefined,\n ...args: TArgs\n) => {\n if (!callback) return;\n\n try {\n const result = callback(...args) as unknown;\n if (\n result !== null &&\n (typeof result === \"object\" || typeof result === \"function\") &&\n \"then\" in result &&\n typeof result.then === \"function\"\n ) {\n void Promise.resolve(result).catch((error) => {\n reportCallbackError(name, error);\n });\n }\n } catch (error) {\n reportCallbackError(name, error);\n }\n};\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 invokeMcpOAuthCallback(\"onError\", optsRef.current.onError, error);\n return;\n }\n\n invokeMcpOAuthCallback(\n \"onComplete\",\n optsRef.current.onComplete,\n serverId,\n );\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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"}
|
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.6",
|
|
4
4
|
"description": "MCP server configuration and connection primitives for @assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
"@modelcontextprotocol/client": "^2.0.0",
|
|
31
31
|
"@modelcontextprotocol/core": "^2.0.0",
|
|
32
32
|
"@radix-ui/react-primitive": "^2.1.10",
|
|
33
|
-
"@assistant-ui/core": "0.3.
|
|
34
|
-
"@assistant-ui/store": "0.3.
|
|
35
|
-
"
|
|
36
|
-
"assistant-
|
|
33
|
+
"@assistant-ui/core": "0.3.7",
|
|
34
|
+
"@assistant-ui/store": "0.3.6",
|
|
35
|
+
"assistant-stream": "0.3.34",
|
|
36
|
+
"@assistant-ui/tap": "0.9.10"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@types/react": "*",
|
|
@@ -45,14 +45,17 @@
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
+
"@testing-library/dom": "^10.4.1",
|
|
49
|
+
"@testing-library/react": "^16.3.2",
|
|
48
50
|
"@types/react": "^19.2.17",
|
|
51
|
+
"jsdom": "^29.1.1",
|
|
49
52
|
"react": "^19.2.8",
|
|
50
53
|
"vitest": "^4.1.10",
|
|
51
54
|
"@assistant-ui/x-buildutils": "0.0.21"
|
|
52
55
|
},
|
|
53
56
|
"publishConfig": {
|
|
54
57
|
"access": "public",
|
|
55
|
-
"provenance":
|
|
58
|
+
"provenance": false
|
|
56
59
|
},
|
|
57
60
|
"homepage": "https://www.assistant-ui.com/",
|
|
58
61
|
"repository": {
|
|
@@ -1,5 +1,104 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { renderHook, waitFor } from "@testing-library/react";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
|
|
6
|
+
const mocks = vi.hoisted(() => {
|
|
7
|
+
const completeAuth = vi.fn<(url: string) => Promise<void>>();
|
|
8
|
+
const server = vi.fn(() => ({ completeAuth }));
|
|
9
|
+
return {
|
|
10
|
+
completeAuth,
|
|
11
|
+
server,
|
|
12
|
+
aui: { mcp: { server } },
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => ({
|
|
17
|
+
...(await importOriginal()),
|
|
18
|
+
useAui: () => mocks.aui,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
const { createMcpOAuthCallbackError, useMcpOAuthCallback } =
|
|
22
|
+
await import("./useMcpOAuthCallback");
|
|
23
|
+
|
|
24
|
+
const callbackUrl =
|
|
25
|
+
"https://app.example.com/oauth/callback?state=aui-mcp%3AZG9jcw.nonce";
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
mocks.completeAuth.mockReset();
|
|
29
|
+
mocks.server.mockClear();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
vi.restoreAllMocks();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("useMcpOAuthCallback", () => {
|
|
37
|
+
it.each(["throws", "rejects"] as const)(
|
|
38
|
+
"keeps successful authentication done when onComplete %s",
|
|
39
|
+
async (mode) => {
|
|
40
|
+
const callbackError = new Error("telemetry failed");
|
|
41
|
+
const consoleError = vi
|
|
42
|
+
.spyOn(console, "error")
|
|
43
|
+
.mockImplementation(() => {});
|
|
44
|
+
const onComplete = vi.fn(() => {
|
|
45
|
+
if (mode === "throws") throw callbackError;
|
|
46
|
+
return Promise.reject(callbackError);
|
|
47
|
+
});
|
|
48
|
+
mocks.completeAuth.mockResolvedValueOnce();
|
|
49
|
+
|
|
50
|
+
const { result } = renderHook(() =>
|
|
51
|
+
useMcpOAuthCallback({ url: callbackUrl, onComplete }),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
await waitFor(() => expect(result.current.status).toBe("done"));
|
|
55
|
+
expect(result.current).toEqual({
|
|
56
|
+
status: "done",
|
|
57
|
+
serverId: "docs",
|
|
58
|
+
error: null,
|
|
59
|
+
});
|
|
60
|
+
await waitFor(() => {
|
|
61
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
62
|
+
"[react-mcp] onComplete callback threw an error",
|
|
63
|
+
callbackError,
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
it.each(["throws", "rejects"] as const)(
|
|
70
|
+
"preserves authentication errors when onError %s",
|
|
71
|
+
async (mode) => {
|
|
72
|
+
const authError = new Error("invalid_grant");
|
|
73
|
+
const callbackError = new Error("telemetry failed");
|
|
74
|
+
const consoleError = vi
|
|
75
|
+
.spyOn(console, "error")
|
|
76
|
+
.mockImplementation(() => {});
|
|
77
|
+
const onError = vi.fn(() => {
|
|
78
|
+
if (mode === "throws") throw callbackError;
|
|
79
|
+
return Promise.reject(callbackError);
|
|
80
|
+
});
|
|
81
|
+
mocks.completeAuth.mockRejectedValueOnce(authError);
|
|
82
|
+
|
|
83
|
+
const { result } = renderHook(() =>
|
|
84
|
+
useMcpOAuthCallback({ url: callbackUrl, onError }),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
await waitFor(() => expect(result.current.status).toBe("error"));
|
|
88
|
+
expect(result.current.serverId).toBe("docs");
|
|
89
|
+
expect(result.current.error).toMatchObject({
|
|
90
|
+
message: 'MCP OAuth callback for server "docs" failed: invalid_grant',
|
|
91
|
+
cause: authError,
|
|
92
|
+
});
|
|
93
|
+
await waitFor(() => {
|
|
94
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
95
|
+
"[react-mcp] onError callback threw an error",
|
|
96
|
+
callbackError,
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
});
|
|
3
102
|
|
|
4
103
|
describe("createMcpOAuthCallbackError", () => {
|
|
5
104
|
it("adds MCP OAuth callback context without a server id", () => {
|
|
@@ -2,6 +2,36 @@ import { type FC, type ReactNode, useEffect, useRef, useState } from "react";
|
|
|
2
2
|
import { useAui } from "@assistant-ui/store";
|
|
3
3
|
import { decodeServerIdFromState } from "../auth/createOAuthProvider";
|
|
4
4
|
|
|
5
|
+
type McpOAuthCallbackName = "onComplete" | "onError";
|
|
6
|
+
|
|
7
|
+
const reportCallbackError = (name: McpOAuthCallbackName, error: unknown) => {
|
|
8
|
+
console.error(`[react-mcp] ${name} callback threw an error`, error);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const invokeMcpOAuthCallback = <TArgs extends unknown[]>(
|
|
12
|
+
name: McpOAuthCallbackName,
|
|
13
|
+
callback: ((...args: TArgs) => void) | undefined,
|
|
14
|
+
...args: TArgs
|
|
15
|
+
) => {
|
|
16
|
+
if (!callback) return;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const result = callback(...args) as unknown;
|
|
20
|
+
if (
|
|
21
|
+
result !== null &&
|
|
22
|
+
(typeof result === "object" || typeof result === "function") &&
|
|
23
|
+
"then" in result &&
|
|
24
|
+
typeof result.then === "function"
|
|
25
|
+
) {
|
|
26
|
+
void Promise.resolve(result).catch((error) => {
|
|
27
|
+
reportCallbackError(name, error);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
reportCallbackError(name, error);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
5
35
|
export const createMcpOAuthCallbackError = (
|
|
6
36
|
err: unknown,
|
|
7
37
|
serverId: string | null,
|
|
@@ -52,7 +82,7 @@ export function useMcpOAuthCallback(
|
|
|
52
82
|
if (startedRef.current === url) return;
|
|
53
83
|
startedRef.current = url;
|
|
54
84
|
|
|
55
|
-
(async () => {
|
|
85
|
+
void (async () => {
|
|
56
86
|
let serverId: string | null = null;
|
|
57
87
|
try {
|
|
58
88
|
const parsed = new URL(url);
|
|
@@ -71,12 +101,18 @@ export function useMcpOAuthCallback(
|
|
|
71
101
|
setResult({ status: "running", serverId, error: null });
|
|
72
102
|
await aui.mcp.server({ id: serverId }).completeAuth(url);
|
|
73
103
|
setResult({ status: "done", serverId, error: null });
|
|
74
|
-
optsRef.current.onComplete?.(serverId);
|
|
75
104
|
} catch (err) {
|
|
76
|
-
const
|
|
77
|
-
setResult({ status: "error", serverId, error
|
|
78
|
-
optsRef.current.onError
|
|
105
|
+
const error = createMcpOAuthCallbackError(err, serverId);
|
|
106
|
+
setResult({ status: "error", serverId, error });
|
|
107
|
+
invokeMcpOAuthCallback("onError", optsRef.current.onError, error);
|
|
108
|
+
return;
|
|
79
109
|
}
|
|
110
|
+
|
|
111
|
+
invokeMcpOAuthCallback(
|
|
112
|
+
"onComplete",
|
|
113
|
+
optsRef.current.onComplete,
|
|
114
|
+
serverId,
|
|
115
|
+
);
|
|
80
116
|
})();
|
|
81
117
|
}, [aui, opts.url]);
|
|
82
118
|
|