@assistant-ui/react-mcp 0.0.8 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/buildHeaders.js.map +1 -1
- package/dist/auth/createOAuthProvider.js.map +1 -1
- package/dist/connector.js.map +1 -1
- package/dist/context/McpConnectorByIndexProvider.js +29 -6
- package/dist/context/McpConnectorByIndexProvider.js.map +1 -1
- package/dist/context/McpCustomServerByIndexProvider.js +29 -6
- package/dist/context/McpCustomServerByIndexProvider.js.map +1 -1
- package/dist/context/McpServerByIdProvider.js +29 -6
- package/dist/context/McpServerByIdProvider.js.map +1 -1
- package/dist/hooks/useMcpOAuthCallback.js +30 -2
- package/dist/hooks/useMcpOAuthCallback.js.map +1 -1
- package/dist/primitives/addForm/McpAddFormAuthFields.js +59 -16
- package/dist/primitives/addForm/McpAddFormAuthFields.js.map +1 -1
- package/dist/primitives/addForm/McpAddFormAuthSelect.js +33 -9
- package/dist/primitives/addForm/McpAddFormAuthSelect.js.map +1 -1
- package/dist/primitives/addForm/McpAddFormCancel.js +24 -7
- package/dist/primitives/addForm/McpAddFormCancel.js.map +1 -1
- package/dist/primitives/addForm/McpAddFormError.js +16 -5
- package/dist/primitives/addForm/McpAddFormError.js.map +1 -1
- package/dist/primitives/addForm/McpAddFormNameField.js +25 -8
- package/dist/primitives/addForm/McpAddFormNameField.js.map +1 -1
- package/dist/primitives/addForm/McpAddFormRoot.js +10 -10
- package/dist/primitives/addForm/McpAddFormRoot.js.map +1 -1
- package/dist/primitives/addForm/McpAddFormSubmit.js +20 -7
- package/dist/primitives/addForm/McpAddFormSubmit.js.map +1 -1
- package/dist/primitives/addForm/McpAddFormUrlField.js +26 -9
- package/dist/primitives/addForm/McpAddFormUrlField.js.map +1 -1
- package/dist/primitives/addForm/context.js.map +1 -1
- package/dist/primitives/addForm.js.map +1 -1
- package/dist/primitives/manager/McpManagerAddCustomTrigger.js +14 -5
- package/dist/primitives/manager/McpManagerAddCustomTrigger.js.map +1 -1
- package/dist/primitives/manager/McpManagerConnectors.js.map +1 -1
- package/dist/primitives/manager/McpManagerCustomServers.js.map +1 -1
- package/dist/primitives/manager/McpManagerRoot.js +19 -6
- package/dist/primitives/manager/McpManagerRoot.js.map +1 -1
- package/dist/primitives/manager.js.map +1 -1
- package/dist/primitives/server/McpServerConnectButton.js +31 -9
- package/dist/primitives/server/McpServerConnectButton.js.map +1 -1
- package/dist/primitives/server/McpServerDisconnectButton.js +28 -8
- package/dist/primitives/server/McpServerDisconnectButton.js.map +1 -1
- package/dist/primitives/server/McpServerError.js +20 -6
- package/dist/primitives/server/McpServerError.js.map +1 -1
- package/dist/primitives/server/McpServerIcon.js +41 -9
- package/dist/primitives/server/McpServerIcon.js.map +1 -1
- package/dist/primitives/server/McpServerName.js +20 -6
- package/dist/primitives/server/McpServerName.js.map +1 -1
- package/dist/primitives/server/McpServerOAuthLink.js +35 -9
- package/dist/primitives/server/McpServerOAuthLink.js.map +1 -1
- package/dist/primitives/server/McpServerRemoveButton.js +28 -8
- package/dist/primitives/server/McpServerRemoveButton.js.map +1 -1
- package/dist/primitives/server/McpServerRoot.js +37 -12
- package/dist/primitives/server/McpServerRoot.js.map +1 -1
- package/dist/primitives/server/McpServerStatus.js +22 -7
- package/dist/primitives/server/McpServerStatus.js.map +1 -1
- package/dist/primitives/server/McpServerToolName.js +18 -6
- package/dist/primitives/server/McpServerToolName.js.map +1 -1
- package/dist/primitives/server/McpServerTools.js +31 -6
- package/dist/primitives/server/McpServerTools.js.map +1 -1
- package/dist/primitives/server.js.map +1 -1
- package/dist/resources/McpManagerResource.d.ts +1 -1
- package/dist/resources/McpManagerResource.d.ts.map +1 -1
- package/dist/resources/McpManagerResource.js +290 -102
- package/dist/resources/McpManagerResource.js.map +1 -1
- package/dist/resources/McpServerResource.d.ts +1 -1
- package/dist/resources/McpServerResource.d.ts.map +1 -1
- package/dist/resources/McpServerResource.js +34 -33
- package/dist/resources/McpServerResource.js.map +1 -1
- package/dist/resources/storage/McpCustomStorage.d.ts +1 -1
- package/dist/resources/storage/McpCustomStorage.d.ts.map +1 -1
- package/dist/resources/storage/McpCustomStorage.js +3 -2
- package/dist/resources/storage/McpCustomStorage.js.map +1 -1
- package/dist/resources/storage/McpLocalStorage.d.ts +1 -1
- package/dist/resources/storage/McpLocalStorage.d.ts.map +1 -1
- package/dist/resources/storage/McpLocalStorage.js +3 -2
- package/dist/resources/storage/McpLocalStorage.js.map +1 -1
- package/dist/resources/storage/McpMemoryStorage.d.ts +1 -1
- package/dist/resources/storage/McpMemoryStorage.d.ts.map +1 -1
- package/dist/resources/storage/McpMemoryStorage.js +3 -2
- package/dist/resources/storage/McpMemoryStorage.js.map +1 -1
- package/dist/utils/serverId.js.map +1 -1
- package/package.json +6 -6
- package/src/resources/McpManagerResource.ts +27 -17
- package/src/resources/McpServerResource.ts +5 -3
- package/src/resources/storage/McpCustomStorage.ts +4 -4
- package/src/resources/storage/McpLocalStorage.ts +4 -4
- package/src/resources/storage/McpMemoryStorage.ts +19 -19
|
@@ -7,7 +7,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
|
7
7
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
8
8
|
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
9
9
|
//#region src/resources/McpServerResource.ts
|
|
10
|
-
const
|
|
10
|
+
const useMcpServerResource = (props) => {
|
|
11
11
|
assertValidServerId(props.id);
|
|
12
12
|
const [connectionState, setConnectionState] = useState("disconnected");
|
|
13
13
|
const [tools, setTools] = useState([]);
|
|
@@ -55,11 +55,11 @@ const McpServerResource = resource(function McpServerResource(props) {
|
|
|
55
55
|
setConnectionState("connected");
|
|
56
56
|
});
|
|
57
57
|
const closeTransport = async () => {
|
|
58
|
-
const
|
|
58
|
+
const t_0 = transportRef.current;
|
|
59
59
|
transportRef.current = null;
|
|
60
60
|
clientRef.current = null;
|
|
61
|
-
if (
|
|
62
|
-
await
|
|
61
|
+
if (t_0) try {
|
|
62
|
+
await t_0.close();
|
|
63
63
|
} catch {}
|
|
64
64
|
};
|
|
65
65
|
const doConnect = useEffectEvent(async () => {
|
|
@@ -68,17 +68,17 @@ const McpServerResource = resource(function McpServerResource(props) {
|
|
|
68
68
|
setLastError(null);
|
|
69
69
|
setAuthorizationUrl(null);
|
|
70
70
|
setTools([]);
|
|
71
|
-
let
|
|
71
|
+
let transport_0 = null;
|
|
72
72
|
try {
|
|
73
|
-
|
|
74
|
-
await finalizeConnect(
|
|
73
|
+
transport_0 = await buildTransport();
|
|
74
|
+
await finalizeConnect(transport_0);
|
|
75
75
|
} catch (err) {
|
|
76
76
|
if (err instanceof UnauthorizedError) {
|
|
77
|
-
transportRef.current =
|
|
77
|
+
transportRef.current = transport_0;
|
|
78
78
|
setConnectionState("authRequired");
|
|
79
79
|
} else {
|
|
80
|
-
if (
|
|
81
|
-
await
|
|
80
|
+
if (transport_0) try {
|
|
81
|
+
await transport_0.close();
|
|
82
82
|
} catch {}
|
|
83
83
|
setLastError({ message: err instanceof Error ? err.message : String(err) });
|
|
84
84
|
setConnectionState("error");
|
|
@@ -97,17 +97,17 @@ const McpServerResource = resource(function McpServerResource(props) {
|
|
|
97
97
|
try {
|
|
98
98
|
const code = new URL(callbackUrl).searchParams.get("code");
|
|
99
99
|
if (!code) throw new Error("missing authorization code in callback URL");
|
|
100
|
-
let
|
|
101
|
-
if (!
|
|
102
|
-
|
|
103
|
-
transportRef.current =
|
|
100
|
+
let transport_1 = transportRef.current;
|
|
101
|
+
if (!transport_1) {
|
|
102
|
+
transport_1 = await buildTransport();
|
|
103
|
+
transportRef.current = transport_1;
|
|
104
104
|
}
|
|
105
|
-
await
|
|
105
|
+
await transport_1.finishAuth(code);
|
|
106
106
|
setAuthorizationUrl(null);
|
|
107
|
-
await finalizeConnect(
|
|
108
|
-
} catch (
|
|
107
|
+
await finalizeConnect(transport_1);
|
|
108
|
+
} catch (err_0) {
|
|
109
109
|
await closeTransport();
|
|
110
|
-
setLastError({ message:
|
|
110
|
+
setLastError({ message: err_0 instanceof Error ? err_0.message : String(err_0) });
|
|
111
111
|
setConnectionState("error");
|
|
112
112
|
}
|
|
113
113
|
});
|
|
@@ -121,22 +121,22 @@ const McpServerResource = resource(function McpServerResource(props) {
|
|
|
121
121
|
doConnect();
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
|
-
const
|
|
124
|
+
const persisted_0 = await props.storage.loadAuthState(props.id);
|
|
125
125
|
if (signal.cancelled) return;
|
|
126
126
|
if (props.auth.type === "oauth") {
|
|
127
|
-
if (!
|
|
128
|
-
} else if (!
|
|
127
|
+
if (!persisted_0?.tokens) return;
|
|
128
|
+
} else if (!persisted_0?.token) return;
|
|
129
129
|
doConnect();
|
|
130
130
|
});
|
|
131
131
|
useEffect(() => {
|
|
132
|
-
const
|
|
133
|
-
tryAutoConnect(
|
|
132
|
+
const signal_0 = { cancelled: false };
|
|
133
|
+
tryAutoConnect(signal_0);
|
|
134
134
|
return () => {
|
|
135
|
-
|
|
136
|
-
const
|
|
135
|
+
signal_0.cancelled = true;
|
|
136
|
+
const t_1 = transportRef.current;
|
|
137
137
|
transportRef.current = null;
|
|
138
138
|
clientRef.current = null;
|
|
139
|
-
if (
|
|
139
|
+
if (t_1) t_1.close().catch(() => {});
|
|
140
140
|
};
|
|
141
141
|
}, []);
|
|
142
142
|
const state = useMemo(() => ({
|
|
@@ -170,21 +170,22 @@ const McpServerResource = resource(function McpServerResource(props) {
|
|
|
170
170
|
await props.onRemove();
|
|
171
171
|
},
|
|
172
172
|
callTool: async (name, args) => {
|
|
173
|
-
const
|
|
174
|
-
if (!
|
|
175
|
-
return await
|
|
173
|
+
const client_0 = clientRef.current;
|
|
174
|
+
if (!client_0) throw new Error(`MCP server "${props.id}" is not connected`);
|
|
175
|
+
return await client_0.callTool({
|
|
176
176
|
name,
|
|
177
177
|
arguments: args
|
|
178
178
|
});
|
|
179
179
|
},
|
|
180
180
|
readResource: async (uri) => {
|
|
181
|
-
const
|
|
182
|
-
if (!
|
|
183
|
-
return await
|
|
181
|
+
const client_1 = clientRef.current;
|
|
182
|
+
if (!client_1) throw new Error(`MCP server "${props.id}" is not connected`);
|
|
183
|
+
return await client_1.readResource({ uri });
|
|
184
184
|
},
|
|
185
185
|
completeAuth: doCompleteAuth
|
|
186
186
|
};
|
|
187
|
-
}
|
|
187
|
+
};
|
|
188
|
+
const McpServerResource = resource(useMcpServerResource);
|
|
188
189
|
//#endregion
|
|
189
190
|
export { McpServerResource };
|
|
190
191
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpServerResource.js","names":[],"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 { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport {\n StreamableHTTPClientTransport,\n type StreamableHTTPClientTransportOptions,\n} from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { UnauthorizedError } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { createOAuthProvider } from \"../auth/createOAuthProvider\";\nimport { buildHeaders } from \"../auth/buildHeaders\";\nimport { assertValidServerId } from \"../utils/serverId\";\nimport type { MCPStorage } from \"./storage/types\";\nimport type {\n MCPAuthConfig,\n MCPConnectionState,\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 onRemove: () => Promise<void>;\n};\n\nexport const McpServerResource = resource(function McpServerResource(\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\n const clientRef = useRef<Client | null>(null);\n const transportRef = useRef<StreamableHTTPClientTransport | null>(null);\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 finalizeConnect = useEffectEvent(\n async (transport: StreamableHTTPClientTransport) => {\n const client = new Client({\n name: \"assistant-ui-mcp\",\n version: \"0.0.0\",\n });\n // SDK's StreamableHTTPClientTransport.sessionId is `string | undefined`\n // but Transport.sessionId is declared `string?` — under\n // exactOptionalPropertyTypes the SDK's own classes don't satisfy its\n // Transport interface. Cast to bridge the gap.\n await client.connect(transport as unknown as Transport);\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 list = await client.listTools();\n clientRef.current = client;\n transportRef.current = transport;\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 setConnectionState(\"connected\");\n },\n );\n\n const closeTransport = async () => {\n const t = transportRef.current;\n transportRef.current = null;\n clientRef.current = null;\n if (t) {\n try {\n await t.close();\n } catch {\n // ignore close errors\n }\n }\n };\n\n const doConnect = useEffectEvent(async () => {\n // Close any prior transport/client so a re-connect doesn't leak.\n await closeTransport();\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 // 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);\n } catch (err) {\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 transportRef.current = transport;\n setConnectionState(\"authRequired\");\n } else {\n if (transport) {\n try {\n await transport.close();\n } catch {\n // ignore close errors\n }\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 setTools([]);\n setAuthorizationUrl(null);\n setConnectionState(\"disconnected\");\n await closeTransport();\n });\n\n const doCompleteAuth = useEffectEvent(async (callbackUrl: string) => {\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 transportRef.current = transport;\n }\n await transport.finishAuth(code);\n setAuthorizationUrl(null);\n await finalizeConnect(transport);\n } catch (err) {\n await closeTransport();\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n setConnectionState(\"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 persisted = await props.storage.loadAuthState(props.id);\n if (signal.cancelled) 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 signal = { cancelled: false };\n void tryAutoConnect(signal);\n return () => {\n signal.cancelled = true;\n const t = transportRef.current;\n transportRef.current = null;\n clientRef.current = null;\n if (t) t.close().catch(() => {});\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 }),\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 ],\n );\n\n return {\n getState: () => state,\n connect: doConnect,\n disconnect: doDisconnect,\n remove: async () => {\n await doDisconnect();\n await props.storage.clearAuthState(props.id);\n await props.onRemove();\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 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 };\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"McpServerResource.js","names":["useState","useRef","useEffect","useMemo","useEffectEvent","resource","ClientOutput","Client","StreamableHTTPClientTransport","StreamableHTTPClientTransportOptions","UnauthorizedError","Transport","createOAuthProvider","buildHeaders","assertValidServerId","MCPStorage","MCPAuthConfig","MCPConnectionState","MCPServerKind","MCPServerState","MCPToolInfo","McpServerResourceProps","id","kind","name","url","icon","auth","storage","redirectUri","autoConnect","onRemove","Promise","useMcpServerResource","props","connectionState","setConnectionState","tools","setTools","lastError","setLastError","message","authorizationUrl","setAuthorizationUrl","clientRef","transportRef","buildTransport","type","authProvider","serverId","config","onAuthorizationUrl","toString","URL","persisted","loadAuthState","headers","transportOpts","requestInit","finalizeConnect","transport","client","version","connect","list","listTools","current","map","t","info","inputSchema","description","undefined","closeTransport","close","doConnect","err","Error","String","doDisconnect","doCompleteAuth","callbackUrl","code","searchParams","get","finishAuth","tryAutoConnect","signal","cancelled","token","tokens","catch","state","getState","disconnect","remove","clearAuthState","callTool","args","arguments","Record","readResource","uri","completeAuth","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 { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport {\n StreamableHTTPClientTransport,\n type StreamableHTTPClientTransportOptions,\n} from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { UnauthorizedError } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { createOAuthProvider } from \"../auth/createOAuthProvider\";\nimport { buildHeaders } from \"../auth/buildHeaders\";\nimport { assertValidServerId } from \"../utils/serverId\";\nimport type { MCPStorage } from \"./storage/types\";\nimport type {\n MCPAuthConfig,\n MCPConnectionState,\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 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\n const clientRef = useRef<Client | null>(null);\n const transportRef = useRef<StreamableHTTPClientTransport | null>(null);\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 finalizeConnect = useEffectEvent(\n async (transport: StreamableHTTPClientTransport) => {\n const client = new Client({\n name: \"assistant-ui-mcp\",\n version: \"0.0.0\",\n });\n // SDK's StreamableHTTPClientTransport.sessionId is `string | undefined`\n // but Transport.sessionId is declared `string?` — under\n // exactOptionalPropertyTypes the SDK's own classes don't satisfy its\n // Transport interface. Cast to bridge the gap.\n await client.connect(transport as unknown as Transport);\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 list = await client.listTools();\n clientRef.current = client;\n transportRef.current = transport;\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 setConnectionState(\"connected\");\n },\n );\n\n const closeTransport = async () => {\n const t = transportRef.current;\n transportRef.current = null;\n clientRef.current = null;\n if (t) {\n try {\n await t.close();\n } catch {\n // ignore close errors\n }\n }\n };\n\n const doConnect = useEffectEvent(async () => {\n // Close any prior transport/client so a re-connect doesn't leak.\n await closeTransport();\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 // 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);\n } catch (err) {\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 transportRef.current = transport;\n setConnectionState(\"authRequired\");\n } else {\n if (transport) {\n try {\n await transport.close();\n } catch {\n // ignore close errors\n }\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 setTools([]);\n setAuthorizationUrl(null);\n setConnectionState(\"disconnected\");\n await closeTransport();\n });\n\n const doCompleteAuth = useEffectEvent(async (callbackUrl: string) => {\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 transportRef.current = transport;\n }\n await transport.finishAuth(code);\n setAuthorizationUrl(null);\n await finalizeConnect(transport);\n } catch (err) {\n await closeTransport();\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n setConnectionState(\"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 persisted = await props.storage.loadAuthState(props.id);\n if (signal.cancelled) 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 signal = { cancelled: false };\n void tryAutoConnect(signal);\n return () => {\n signal.cancelled = true;\n const t = transportRef.current;\n transportRef.current = null;\n clientRef.current = null;\n if (t) t.close().catch(() => {});\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 }),\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 ],\n );\n\n return {\n getState: () => state,\n connect: doConnect,\n disconnect: doDisconnect,\n remove: async () => {\n await doDisconnect();\n await props.storage.clearAuthState(props.id);\n await props.onRemove();\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 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 };\n};\n\nexport const McpServerResource = resource(useMcpServerResource);\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MCPStorage } from "./types.js";
|
|
2
2
|
|
|
3
3
|
//#region src/resources/storage/McpCustomStorage.d.ts
|
|
4
|
-
declare const McpCustomStorage:
|
|
4
|
+
declare const McpCustomStorage: import("@assistant-ui/tap").Resource<MCPStorage, [impl: MCPStorage]>;
|
|
5
5
|
//#endregion
|
|
6
6
|
export { McpCustomStorage };
|
|
7
7
|
//# sourceMappingURL=McpCustomStorage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpCustomStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpCustomStorage.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"McpCustomStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpCustomStorage.ts"],"mappings":";;;cAOa,gBAAA,8BAAgB,QAAA,CAAA,UAAA,GAAA,IAAA,EAAA,UAAA"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { resource } from "@assistant-ui/tap";
|
|
2
2
|
//#region src/resources/storage/McpCustomStorage.ts
|
|
3
|
-
const
|
|
3
|
+
const useMcpCustomStorage = (impl) => {
|
|
4
4
|
return impl;
|
|
5
|
-
}
|
|
5
|
+
};
|
|
6
|
+
const McpCustomStorage = resource(useMcpCustomStorage);
|
|
6
7
|
//#endregion
|
|
7
8
|
export { McpCustomStorage };
|
|
8
9
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpCustomStorage.js","names":[],"sources":["../../../src/resources/storage/McpCustomStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPStorage } from \"./types\";\n\
|
|
1
|
+
{"version":3,"file":"McpCustomStorage.js","names":["resource","MCPStorage","useMcpCustomStorage","impl","McpCustomStorage"],"sources":["../../../src/resources/storage/McpCustomStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPStorage } from \"./types\";\n\nconst useMcpCustomStorage = (impl: MCPStorage): MCPStorage => {\n return impl;\n};\n\nexport const McpCustomStorage = resource(useMcpCustomStorage);\n"],"mappings":";;AAGA,MAAME,uBAAuBC,SAAiC;CAC5D,OAAOA;AACT;AAEA,MAAaC,mBAAmBJ,SAASE,mBAAmB"}
|
|
@@ -5,7 +5,7 @@ type McpLocalStorageOptions = {
|
|
|
5
5
|
/** Namespace prefix for keys. Default "aui-mcp". */keyPrefix?: string; /** Override the underlying Storage. Defaults to globalThis.localStorage. */
|
|
6
6
|
storage?: Storage;
|
|
7
7
|
};
|
|
8
|
-
declare const McpLocalStorage:
|
|
8
|
+
declare const McpLocalStorage: import("@assistant-ui/tap").Resource<MCPStorage, [opts?: McpLocalStorageOptions | undefined]>;
|
|
9
9
|
//#endregion
|
|
10
10
|
export { McpLocalStorage, McpLocalStorageOptions };
|
|
11
11
|
//# sourceMappingURL=McpLocalStorage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpLocalStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"mappings":";;;KAKY,sBAAA;sDAEV,SAAA,WAFgC;EAIhC,OAAA,GAAU,OAAO;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"McpLocalStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"mappings":";;;KAKY,sBAAA;sDAEV,SAAA,WAFgC;EAIhC,OAAA,GAAU,OAAO;AAAA;AAAA,cAmEN,eAAA,8BAAe,QAAA,CAAA,UAAA,GAAA,IAAA,GAAA,sBAAA"}
|
|
@@ -9,7 +9,7 @@ function resolveStorage(opts) {
|
|
|
9
9
|
}
|
|
10
10
|
return null;
|
|
11
11
|
}
|
|
12
|
-
const
|
|
12
|
+
const useMcpLocalStorage = (opts = {}) => {
|
|
13
13
|
const prefix = opts.keyPrefix ?? "aui-mcp";
|
|
14
14
|
const customServersKey = `${prefix}:custom-servers`;
|
|
15
15
|
const authKey = (id) => `${prefix}:auth:${id}`;
|
|
@@ -49,7 +49,8 @@ const McpLocalStorage = resource(function McpLocalStorage(opts = {}) {
|
|
|
49
49
|
remove(authKey(id));
|
|
50
50
|
}
|
|
51
51
|
};
|
|
52
|
-
}
|
|
52
|
+
};
|
|
53
|
+
const McpLocalStorage = resource(useMcpLocalStorage);
|
|
53
54
|
//#endregion
|
|
54
55
|
export { McpLocalStorage };
|
|
55
56
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpLocalStorage.js","names":[],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPCustomServerRecord } from \"../../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"../../auth/types\";\nimport type { MCPStorage } from \"./types\";\n\nexport type McpLocalStorageOptions = {\n /** Namespace prefix for keys. Default \"aui-mcp\". */\n keyPrefix?: string;\n /** Override the underlying Storage. Defaults to globalThis.localStorage. */\n storage?: Storage;\n};\n\nfunction resolveStorage(opts: McpLocalStorageOptions): Storage | null {\n if (opts.storage) return opts.storage;\n if (typeof globalThis !== \"undefined\" && \"localStorage\" in globalThis) {\n try {\n return (globalThis as { localStorage: Storage }).localStorage;\n } catch {\n return null;\n }\n }\n return null;\n}\n\
|
|
1
|
+
{"version":3,"file":"McpLocalStorage.js","names":["resource","MCPCustomServerRecord","MCPPersistedAuthState","MCPStorage","McpLocalStorageOptions","keyPrefix","storage","Storage","resolveStorage","opts","globalThis","localStorage","useMcpLocalStorage","prefix","customServersKey","authKey","id","read","key","fallback","T","raw","getItem","JSON","parse","write","value","setItem","stringify","remove","removeItem","loadCustomServers","saveCustomServers","records","loadAuthState","saveAuthState","state","clearAuthState","McpLocalStorage"],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPCustomServerRecord } from \"../../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"../../auth/types\";\nimport type { MCPStorage } from \"./types\";\n\nexport type McpLocalStorageOptions = {\n /** Namespace prefix for keys. Default \"aui-mcp\". */\n keyPrefix?: string;\n /** Override the underlying Storage. Defaults to globalThis.localStorage. */\n storage?: Storage;\n};\n\nfunction resolveStorage(opts: McpLocalStorageOptions): Storage | null {\n if (opts.storage) return opts.storage;\n if (typeof globalThis !== \"undefined\" && \"localStorage\" in globalThis) {\n try {\n return (globalThis as { localStorage: Storage }).localStorage;\n } catch {\n return null;\n }\n }\n return null;\n}\n\nconst useMcpLocalStorage = (opts: McpLocalStorageOptions = {}): MCPStorage => {\n const prefix = opts.keyPrefix ?? \"aui-mcp\";\n const customServersKey = `${prefix}:custom-servers`;\n const authKey = (id: string) => `${prefix}:auth:${id}`;\n const storage = resolveStorage(opts);\n\n const read = <T>(key: string, fallback: T): T => {\n if (!storage) return fallback;\n try {\n const raw = storage.getItem(key);\n if (raw == null) return fallback;\n return JSON.parse(raw) as T;\n } catch {\n return fallback;\n }\n };\n\n const write = (key: string, value: unknown): void => {\n if (!storage) return;\n try {\n storage.setItem(key, JSON.stringify(value));\n } catch {\n // quota or serialization failure — silently drop\n }\n };\n\n const remove = (key: string): void => {\n if (!storage) return;\n try {\n storage.removeItem(key);\n } catch {\n // ignore\n }\n };\n\n return {\n loadCustomServers: async () =>\n read<MCPCustomServerRecord[]>(customServersKey, []),\n saveCustomServers: async (records) => {\n write(customServersKey, records);\n },\n loadAuthState: async (id) =>\n read<MCPPersistedAuthState | null>(authKey(id), null),\n saveAuthState: async (id, state) => {\n write(authKey(id), state);\n },\n clearAuthState: async (id) => {\n remove(authKey(id));\n },\n };\n};\n\nexport const McpLocalStorage = resource(useMcpLocalStorage);\n"],"mappings":";;AAYA,SAASQ,eAAeC,MAA8C;CACpE,IAAIA,KAAKH,SAAS,OAAOG,KAAKH;CAC9B,IAAI,OAAOI,eAAe,eAAe,kBAAkBA,YACzD,IAAI;EACF,OAAQA,WAAyCC;CACnD,QAAQ;EACN,OAAO;CACT;CAEF,OAAO;AACT;AAEA,MAAMC,sBAAsBH,OAA+B,CAAC,MAAkB;CAC5E,MAAMI,SAASJ,KAAKJ,aAAa;CACjC,MAAMS,mBAAmB,GAAGD,OAAM;CAClC,MAAME,WAAWC,OAAe,GAAGH,OAAM,QAASG;CAClD,MAAMV,UAAUE,eAAeC,IAAI;CAEnC,MAAMQ,QAAWC,KAAaC,aAAmB;EAC/C,IAAI,CAACb,SAAS,OAAOa;EACrB,IAAI;GACF,MAAME,MAAMf,QAAQgB,QAAQJ,GAAG;GAC/B,IAAIG,OAAO,MAAM,OAAOF;GACxB,OAAOI,KAAKC,MAAMH,GAAG;EACvB,QAAQ;GACN,OAAOF;EACT;CACF;CAEA,MAAMM,SAASP,KAAaQ,UAAyB;EACnD,IAAI,CAACpB,SAAS;EACd,IAAI;GACFA,QAAQqB,QAAQT,KAAKK,KAAKK,UAAUF,KAAK,CAAC;EAC5C,QAAQ,CACN;CAEJ;CAEA,MAAMG,UAAUX,QAAsB;EACpC,IAAI,CAACZ,SAAS;EACd,IAAI;GACFA,QAAQwB,WAAWZ,GAAG;EACxB,QAAQ,CACN;CAEJ;CAEA,OAAO;EACLa,mBAAmB,YACjBd,KAA8BH,kBAAkB,CAAA,CAAE;EACpDkB,mBAAmB,OAAOC,YAAY;GACpCR,MAAMX,kBAAkBmB,OAAO;EACjC;EACAC,eAAe,OAAOlB,OACpBC,KAAmCF,QAAQC,EAAE,GAAG,IAAI;EACtDmB,eAAe,OAAOnB,IAAIoB,UAAU;GAClCX,MAAMV,QAAQC,EAAE,GAAGoB,KAAK;EAC1B;EACAC,gBAAgB,OAAOrB,OAAO;GAC5Ba,OAAOd,QAAQC,EAAE,CAAC;EACpB;CACF;AACF;AAEA,MAAasB,kBAAkBtC,SAASY,kBAAkB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MCPStorage } from "./types.js";
|
|
2
2
|
|
|
3
3
|
//#region src/resources/storage/McpMemoryStorage.d.ts
|
|
4
|
-
declare const McpMemoryStorage:
|
|
4
|
+
declare const McpMemoryStorage: import("@assistant-ui/tap").Resource<MCPStorage, []>;
|
|
5
5
|
//#endregion
|
|
6
6
|
export { McpMemoryStorage };
|
|
7
7
|
//# sourceMappingURL=McpMemoryStorage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpMemoryStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpMemoryStorage.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"McpMemoryStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpMemoryStorage.ts"],"mappings":";;;cAuBa,gBAAA,8BAAgB,QAAA,CAAA,UAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resource } from "@assistant-ui/tap";
|
|
2
2
|
//#region src/resources/storage/McpMemoryStorage.ts
|
|
3
|
-
const
|
|
3
|
+
const useMcpMemoryStorage = () => {
|
|
4
4
|
let servers = [];
|
|
5
5
|
const auth = /* @__PURE__ */ new Map();
|
|
6
6
|
return {
|
|
@@ -16,7 +16,8 @@ const McpMemoryStorage = resource(function McpMemoryStorage() {
|
|
|
16
16
|
auth.delete(id);
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
|
-
}
|
|
19
|
+
};
|
|
20
|
+
const McpMemoryStorage = resource(useMcpMemoryStorage);
|
|
20
21
|
//#endregion
|
|
21
22
|
export { McpMemoryStorage };
|
|
22
23
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpMemoryStorage.js","names":[],"sources":["../../../src/resources/storage/McpMemoryStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPCustomServerRecord } from \"../../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"../../auth/types\";\nimport type { MCPStorage } from \"./types\";\n\
|
|
1
|
+
{"version":3,"file":"McpMemoryStorage.js","names":["resource","MCPCustomServerRecord","MCPPersistedAuthState","MCPStorage","useMcpMemoryStorage","servers","auth","Map","loadCustomServers","saveCustomServers","records","loadAuthState","id","get","saveAuthState","state","set","clearAuthState","delete","McpMemoryStorage"],"sources":["../../../src/resources/storage/McpMemoryStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPCustomServerRecord } from \"../../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"../../auth/types\";\nimport type { MCPStorage } from \"./types\";\n\nconst useMcpMemoryStorage = (): MCPStorage => {\n let servers: MCPCustomServerRecord[] = [];\n const auth = new Map<string, MCPPersistedAuthState>();\n return {\n loadCustomServers: async () => [...servers],\n saveCustomServers: async (records) => {\n servers = [...records];\n },\n loadAuthState: async (id) => auth.get(id) ?? null,\n saveAuthState: async (id, state) => {\n auth.set(id, state);\n },\n clearAuthState: async (id) => {\n auth.delete(id);\n },\n };\n};\n\nexport const McpMemoryStorage = resource(useMcpMemoryStorage);\n"],"mappings":";;AAKA,MAAMI,4BAAwC;CAC5C,IAAIC,UAAmC,CAAA;CACvC,MAAMC,uBAAO,IAAIC,IAAmC;CACpD,OAAO;EACLC,mBAAmB,YAAY,CAAC,GAAGH,OAAO;EAC1CI,mBAAmB,OAAOC,YAAY;GACpCL,UAAU,CAAC,GAAGK,OAAO;EACvB;EACAC,eAAe,OAAOC,OAAON,KAAKO,IAAID,EAAE,KAAK;EAC7CE,eAAe,OAAOF,IAAIG,UAAU;GAClCT,KAAKU,IAAIJ,IAAIG,KAAK;EACpB;EACAE,gBAAgB,OAAOL,OAAO;GAC5BN,KAAKY,OAAON,EAAE;EAChB;CACF;AACF;AAEA,MAAaO,mBAAmBnB,SAASI,mBAAmB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverId.js","names":[],"sources":["../../src/utils/serverId.ts"],"sourcesContent":["// Server ids are joined with tool names via `__` to form unique tool\n// names for the AI SDK. The separator must not appear inside ids,\n// otherwise two distinct `(serverId, toolName)` pairs could produce\n// the same composite name.\nexport function assertValidServerId(id: string): void {\n if (id.length === 0) {\n throw new Error(\"MCP server id must not be empty.\");\n }\n if (id.includes(\"__\")) {\n throw new Error(\n `MCP server id \"${id}\" must not contain \"__\" — that sequence is reserved for separating server id from tool name.`,\n );\n }\n if (id.includes(\"\\x1f\")) {\n throw new Error(\n `MCP server id \"${id}\" must not contain the ASCII Unit Separator (\\\\x1f) — it is reserved as the list delimiter in primitive id-list selectors.`,\n );\n }\n}\n"],"mappings":";AAIA,
|
|
1
|
+
{"version":3,"file":"serverId.js","names":["assertValidServerId","id","length","Error","includes"],"sources":["../../src/utils/serverId.ts"],"sourcesContent":["// Server ids are joined with tool names via `__` to form unique tool\n// names for the AI SDK. The separator must not appear inside ids,\n// otherwise two distinct `(serverId, toolName)` pairs could produce\n// the same composite name.\nexport function assertValidServerId(id: string): void {\n if (id.length === 0) {\n throw new Error(\"MCP server id must not be empty.\");\n }\n if (id.includes(\"__\")) {\n throw new Error(\n `MCP server id \"${id}\" must not contain \"__\" — that sequence is reserved for separating server id from tool name.`,\n );\n }\n if (id.includes(\"\\x1f\")) {\n throw new Error(\n `MCP server id \"${id}\" must not contain the ASCII Unit Separator (\\\\x1f) — it is reserved as the list delimiter in primitive id-list selectors.`,\n );\n }\n}\n"],"mappings":";AAIA,SAAgBA,oBAAoBC,IAAkB;CACpD,IAAIA,GAAGC,WAAW,GAChB,MAAM,IAAIC,MAAM,kCAAkC;CAEpD,IAAIF,GAAGG,SAAS,IAAI,GAClB,MAAM,IAAID,MACR,kBAAkBF,GAAE,6FACtB;CAEF,IAAIA,GAAGG,SAAS,GAAM,GACpB,MAAM,IAAID,MACR,kBAAkBF,GAAE,2HACtB;AAEJ"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "MCP server configuration and connection primitives for @assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
31
31
|
"@radix-ui/react-primitive": "^2.1.5",
|
|
32
|
-
"@assistant-ui/core": "0.2.
|
|
33
|
-
"@assistant-ui/store": "0.2.
|
|
34
|
-
"@assistant-ui/tap": "0.
|
|
35
|
-
"assistant-stream": "0.3.
|
|
32
|
+
"@assistant-ui/core": "0.2.15",
|
|
33
|
+
"@assistant-ui/store": "0.2.17",
|
|
34
|
+
"@assistant-ui/tap": "0.8.1",
|
|
35
|
+
"assistant-stream": "0.3.22"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@types/react": "*",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/react": "^19.2.17",
|
|
48
48
|
"react": "^19.2.7",
|
|
49
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
49
|
+
"@assistant-ui/x-buildutils": "0.0.13"
|
|
50
50
|
},
|
|
51
51
|
"publishConfig": {
|
|
52
52
|
"access": "public",
|
|
@@ -36,9 +36,9 @@ function defaultRedirectUri(): string {
|
|
|
36
36
|
// array each render (which would invalidate the serverElements memo below).
|
|
37
37
|
const NO_CONNECTORS: MCPConnector[] = [];
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
const useMcpManagerResource = (
|
|
40
40
|
props: McpManagerResourceProps,
|
|
41
|
-
): ClientOutput<"mcp"> {
|
|
41
|
+
): ClientOutput<"mcp"> => {
|
|
42
42
|
const connectors = props.connectors ?? NO_CONNECTORS;
|
|
43
43
|
const autoConnect = props.autoConnect ?? true;
|
|
44
44
|
const redirectUri = props.oauthRedirectUri ?? defaultRedirectUri();
|
|
@@ -54,23 +54,31 @@ export const McpManagerResource = resource(function McpManagerResource(
|
|
|
54
54
|
const hydratedRef = useRef(false);
|
|
55
55
|
|
|
56
56
|
const hydrate = useEffectEvent(async (signal: { cancelled: boolean }) => {
|
|
57
|
-
|
|
58
|
-
const records = await storage.loadCustomServers();
|
|
59
|
-
if (signal.cancelled) return;
|
|
60
|
-
// Merge rather than replace so any addCustomServer calls that
|
|
61
|
-
// happened before hydration resolved aren't silently overwritten.
|
|
62
|
-
// Persisted order wins; pre-hydration locals append.
|
|
63
|
-
setCustomServers((prev) => {
|
|
64
|
-
if (prev.length === 0) return records;
|
|
65
|
-
const persistedIds = new Set(records.map((r) => r.id));
|
|
66
|
-
return [...records, ...prev.filter((r) => !persistedIds.has(r.id))];
|
|
67
|
-
});
|
|
68
|
-
} finally {
|
|
57
|
+
const markHydrated = () => {
|
|
69
58
|
if (!signal.cancelled) {
|
|
70
59
|
hydratedRef.current = true;
|
|
71
60
|
setIsHydrated(true);
|
|
72
61
|
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
let records: Awaited<ReturnType<typeof storage.loadCustomServers>>;
|
|
65
|
+
try {
|
|
66
|
+
records = await storage.loadCustomServers();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
markHydrated();
|
|
69
|
+
throw error;
|
|
73
70
|
}
|
|
71
|
+
|
|
72
|
+
if (signal.cancelled) return;
|
|
73
|
+
// Merge rather than replace so any addCustomServer calls that
|
|
74
|
+
// happened before hydration resolved aren't silently overwritten.
|
|
75
|
+
// Persisted order wins; pre-hydration locals append.
|
|
76
|
+
setCustomServers((prev) => {
|
|
77
|
+
if (prev.length === 0) return records;
|
|
78
|
+
const persistedIds = new Set(records.map((r) => r.id));
|
|
79
|
+
return [...records, ...prev.filter((r) => !persistedIds.has(r.id))];
|
|
80
|
+
});
|
|
81
|
+
markHydrated();
|
|
74
82
|
});
|
|
75
83
|
|
|
76
84
|
useEffect(() => {
|
|
@@ -133,7 +141,7 @@ export const McpManagerResource = resource(function McpManagerResource(
|
|
|
133
141
|
return [...connectorElements, ...customElements];
|
|
134
142
|
}, [connectors, customServers, storage, redirectUri, autoConnect]);
|
|
135
143
|
|
|
136
|
-
const lookup = useClientLookup(
|
|
144
|
+
const lookup = useClientLookup(serverElements);
|
|
137
145
|
|
|
138
146
|
const state = useMemo<MCPManagerState>(() => {
|
|
139
147
|
const all = lookup.state;
|
|
@@ -234,12 +242,14 @@ export const McpManagerResource = resource(function McpManagerResource(
|
|
|
234
242
|
}
|
|
235
243
|
},
|
|
236
244
|
};
|
|
237
|
-
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
export const McpManagerResource = resource(useMcpManagerResource);
|
|
238
248
|
|
|
239
249
|
// Ensure modelContext exists as a sibling when the manager mounts. If an
|
|
240
250
|
// ancestor (e.g. a chat runtime) already provides modelContext, this is a
|
|
241
251
|
// no-op; otherwise it's auto-mounted alongside `mcp`.
|
|
242
|
-
attachTransformScopes(
|
|
252
|
+
attachTransformScopes(useMcpManagerResource, (scopes, parent) => {
|
|
243
253
|
if (!scopes.modelContext && parent.modelContext.source === null) {
|
|
244
254
|
scopes.modelContext = ModelContext();
|
|
245
255
|
}
|
|
@@ -33,9 +33,9 @@ export type McpServerResourceProps = {
|
|
|
33
33
|
onRemove: () => Promise<void>;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
const useMcpServerResource = (
|
|
37
37
|
props: McpServerResourceProps,
|
|
38
|
-
): ClientOutput<"mcpServer"> {
|
|
38
|
+
): ClientOutput<"mcpServer"> => {
|
|
39
39
|
assertValidServerId(props.id);
|
|
40
40
|
const [connectionState, setConnectionState] =
|
|
41
41
|
useState<MCPConnectionState>("disconnected");
|
|
@@ -278,4 +278,6 @@ export const McpServerResource = resource(function McpServerResource(
|
|
|
278
278
|
},
|
|
279
279
|
completeAuth: doCompleteAuth,
|
|
280
280
|
};
|
|
281
|
-
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
export const McpServerResource = resource(useMcpServerResource);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { resource } from "@assistant-ui/tap";
|
|
2
2
|
import type { MCPStorage } from "./types";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
impl: MCPStorage,
|
|
6
|
-
): MCPStorage {
|
|
4
|
+
const useMcpCustomStorage = (impl: MCPStorage): MCPStorage => {
|
|
7
5
|
return impl;
|
|
8
|
-
}
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const McpCustomStorage = resource(useMcpCustomStorage);
|
|
@@ -22,9 +22,7 @@ function resolveStorage(opts: McpLocalStorageOptions): Storage | null {
|
|
|
22
22
|
return null;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
opts: McpLocalStorageOptions = {},
|
|
27
|
-
): MCPStorage {
|
|
25
|
+
const useMcpLocalStorage = (opts: McpLocalStorageOptions = {}): MCPStorage => {
|
|
28
26
|
const prefix = opts.keyPrefix ?? "aui-mcp";
|
|
29
27
|
const customServersKey = `${prefix}:custom-servers`;
|
|
30
28
|
const authKey = (id: string) => `${prefix}:auth:${id}`;
|
|
@@ -74,4 +72,6 @@ export const McpLocalStorage = resource(function McpLocalStorage(
|
|
|
74
72
|
remove(authKey(id));
|
|
75
73
|
},
|
|
76
74
|
};
|
|
77
|
-
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const McpLocalStorage = resource(useMcpLocalStorage);
|
|
@@ -3,22 +3,22 @@ import type { MCPCustomServerRecord } from "../../mcp-scope";
|
|
|
3
3
|
import type { MCPPersistedAuthState } from "../../auth/types";
|
|
4
4
|
import type { MCPStorage } from "./types";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
);
|
|
6
|
+
const useMcpMemoryStorage = (): MCPStorage => {
|
|
7
|
+
let servers: MCPCustomServerRecord[] = [];
|
|
8
|
+
const auth = new Map<string, MCPPersistedAuthState>();
|
|
9
|
+
return {
|
|
10
|
+
loadCustomServers: async () => [...servers],
|
|
11
|
+
saveCustomServers: async (records) => {
|
|
12
|
+
servers = [...records];
|
|
13
|
+
},
|
|
14
|
+
loadAuthState: async (id) => auth.get(id) ?? null,
|
|
15
|
+
saveAuthState: async (id, state) => {
|
|
16
|
+
auth.set(id, state);
|
|
17
|
+
},
|
|
18
|
+
clearAuthState: async (id) => {
|
|
19
|
+
auth.delete(id);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const McpMemoryStorage = resource(useMcpMemoryStorage);
|