shreyask commited on
Commit
c211e0e
·
verified ·
1 Parent(s): d63a244

add mcp support

Browse files
src/components/OAuthCallback.tsx ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useState } from "react";
2
+ import { exchangeCodeForToken } from "../services/oauth";
3
+ import { secureStorage } from "../utils/storage";
4
+ import type { MCPServerConfig } from "../types/mcp";
5
+ import { STORAGE_KEYS, DEFAULTS } from "../config/constants";
6
+
7
+ interface OAuthTokens {
8
+ access_token: string;
9
+ refresh_token?: string;
10
+ expires_in?: number;
11
+ token_type?: string;
12
+ [key: string]: string | number | undefined;
13
+ }
14
+
15
+ interface OAuthCallbackProps {
16
+ serverUrl: string;
17
+ onSuccess?: (tokens: OAuthTokens) => void;
18
+ onError?: (error: Error) => void;
19
+ }
20
+
21
+ const OAuthCallback: React.FC<OAuthCallbackProps> = ({
22
+ serverUrl,
23
+ onSuccess,
24
+ onError,
25
+ }) => {
26
+ const [status, setStatus] = useState<string>("Authorizing...");
27
+
28
+ useEffect(() => {
29
+ const params = new URLSearchParams(window.location.search);
30
+ const code = params.get("code");
31
+ // Always persist MCP server URL for robustness
32
+ localStorage.setItem(STORAGE_KEYS.OAUTH_MCP_SERVER_URL, serverUrl);
33
+ if (code) {
34
+ exchangeCodeForToken({
35
+ serverUrl,
36
+ code,
37
+ redirectUri: window.location.origin + DEFAULTS.OAUTH_REDIRECT_PATH,
38
+ })
39
+ .then(async (tokens) => {
40
+ await secureStorage.setItem(STORAGE_KEYS.OAUTH_ACCESS_TOKEN, tokens.access_token);
41
+ // Add MCP server to MCPClientService for UI
42
+ const mcpServerUrl = localStorage.getItem(STORAGE_KEYS.OAUTH_MCP_SERVER_URL);
43
+ if (mcpServerUrl) {
44
+ // Use persisted name and transport from initial add
45
+ const serverName =
46
+ localStorage.getItem(STORAGE_KEYS.MCP_SERVER_NAME) || mcpServerUrl;
47
+ const serverTransport =
48
+ (localStorage.getItem(STORAGE_KEYS.MCP_SERVER_TRANSPORT) as MCPServerConfig['transport']) || DEFAULTS.MCP_TRANSPORT;
49
+ // Build config and add to mcp-servers
50
+ const serverConfig = {
51
+ id: `server_${Date.now()}`,
52
+ name: serverName,
53
+ url: mcpServerUrl,
54
+ enabled: true,
55
+ transport: serverTransport,
56
+ auth: {
57
+ type: "bearer" as const,
58
+ token: tokens.access_token,
59
+ },
60
+ };
61
+ // Load existing servers
62
+ let servers: MCPServerConfig[] = [];
63
+ try {
64
+ const stored = localStorage.getItem(STORAGE_KEYS.MCP_SERVERS);
65
+ if (stored) servers = JSON.parse(stored);
66
+ } catch {}
67
+ // Add or update
68
+ const exists = servers.some((s: MCPServerConfig) => s.url === mcpServerUrl);
69
+ if (!exists) {
70
+ servers.push(serverConfig);
71
+ localStorage.setItem(STORAGE_KEYS.MCP_SERVERS, JSON.stringify(servers));
72
+ }
73
+ // Clear temp values from localStorage for clean slate
74
+ localStorage.removeItem(STORAGE_KEYS.MCP_SERVER_NAME);
75
+ localStorage.removeItem(STORAGE_KEYS.MCP_SERVER_TRANSPORT);
76
+ localStorage.removeItem(STORAGE_KEYS.OAUTH_MCP_SERVER_URL);
77
+ }
78
+ setStatus("Authorization successful! Redirecting...");
79
+ if (onSuccess) onSuccess(tokens);
80
+ // Redirect to main app page after short delay
81
+ setTimeout(() => {
82
+ window.location.replace("/");
83
+ }, 1000);
84
+ })
85
+ .catch((err) => {
86
+ setStatus("OAuth token exchange failed: " + err.message);
87
+ if (onError) onError(err);
88
+ });
89
+ } else {
90
+ setStatus("Missing authorization code in callback URL.");
91
+ }
92
+ }, [serverUrl, onSuccess, onError]);
93
+
94
+ return <div>{status}</div>;
95
+ };
96
+
97
+ export default OAuthCallback;
src/config/constants.ts ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Application configuration constants
3
+ */
4
+
5
+ // MCP Client Configuration
6
+ export const MCP_CLIENT_CONFIG = {
7
+ NAME: "LFM2-WebGPU",
8
+ VERSION: "1.0.0",
9
+ TEST_CLIENT_NAME: "LFM2-WebGPU-Test",
10
+ } as const;
11
+
12
+ // Storage Keys
13
+ export const STORAGE_KEYS = {
14
+ MCP_SERVERS: "mcp-servers",
15
+ OAUTH_CLIENT_ID: "oauth_client_id",
16
+ OAUTH_CLIENT_SECRET: "oauth_client_secret",
17
+ OAUTH_AUTHORIZATION_ENDPOINT: "oauth_authorization_endpoint",
18
+ OAUTH_TOKEN_ENDPOINT: "oauth_token_endpoint",
19
+ OAUTH_REDIRECT_URI: "oauth_redirect_uri",
20
+ OAUTH_RESOURCE: "oauth_resource",
21
+ OAUTH_ACCESS_TOKEN: "oauth_access_token",
22
+ OAUTH_CODE_VERIFIER: "oauth_code_verifier",
23
+ OAUTH_MCP_SERVER_URL: "oauth_mcp_server_url",
24
+ OAUTH_AUTHORIZATION_SERVER_METADATA: "oauth_authorization_server_metadata",
25
+ MCP_SERVER_NAME: "mcp_server_name",
26
+ MCP_SERVER_TRANSPORT: "mcp_server_transport",
27
+ } as const;
28
+
29
+ // Default Values
30
+ export const DEFAULTS = {
31
+ MCP_TRANSPORT: "streamable-http" as const,
32
+ OAUTH_REDIRECT_PATH: "/oauth/callback",
33
+ NOTIFICATION_TIMEOUT: 3000,
34
+ OAUTH_ERROR_TIMEOUT: 5000,
35
+ } as const;
src/utils/storage.ts ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Secure storage utilities for sensitive data like OAuth tokens
3
+ */
4
+
5
+ const ENCRYPTION_KEY_NAME = 'mcp-encryption-key';
6
+
7
+ // Generate or retrieve encryption key
8
+ async function getEncryptionKey(): Promise<CryptoKey> {
9
+ const keyData = localStorage.getItem(ENCRYPTION_KEY_NAME);
10
+
11
+ if (keyData) {
12
+ try {
13
+ const keyBuffer = new Uint8Array(JSON.parse(keyData));
14
+ return await crypto.subtle.importKey(
15
+ 'raw',
16
+ keyBuffer,
17
+ { name: 'AES-GCM' },
18
+ false,
19
+ ['encrypt', 'decrypt']
20
+ );
21
+ } catch {
22
+ // Key corrupted, generate new one
23
+ }
24
+ }
25
+
26
+ // Generate new key
27
+ const key = await crypto.subtle.generateKey(
28
+ { name: 'AES-GCM', length: 256 },
29
+ true,
30
+ ['encrypt', 'decrypt']
31
+ );
32
+
33
+ // Store key for future use
34
+ const keyBuffer = await crypto.subtle.exportKey('raw', key);
35
+ localStorage.setItem(ENCRYPTION_KEY_NAME, JSON.stringify(Array.from(new Uint8Array(keyBuffer))));
36
+
37
+ return key;
38
+ }
39
+
40
+ // Encrypt sensitive data
41
+ export async function encryptData(data: string): Promise<string> {
42
+ try {
43
+ const key = await getEncryptionKey();
44
+ const encoder = new TextEncoder();
45
+ const dataBuffer = encoder.encode(data);
46
+
47
+ const iv = crypto.getRandomValues(new Uint8Array(12));
48
+ const encryptedBuffer = await crypto.subtle.encrypt(
49
+ { name: 'AES-GCM', iv },
50
+ key,
51
+ dataBuffer
52
+ );
53
+
54
+ // Combine IV and encrypted data
55
+ const result = new Uint8Array(iv.length + encryptedBuffer.byteLength);
56
+ result.set(iv);
57
+ result.set(new Uint8Array(encryptedBuffer), iv.length);
58
+
59
+ return btoa(String.fromCharCode(...result));
60
+ } catch (error) {
61
+ console.warn('Encryption failed, storing data unencrypted:', error);
62
+ return data;
63
+ }
64
+ }
65
+
66
+ // Decrypt sensitive data
67
+ export async function decryptData(encryptedData: string): Promise<string> {
68
+ try {
69
+ const key = await getEncryptionKey();
70
+ const dataBuffer = new Uint8Array(
71
+ atob(encryptedData).split('').map(char => char.charCodeAt(0))
72
+ );
73
+
74
+ const iv = dataBuffer.slice(0, 12);
75
+ const encrypted = dataBuffer.slice(12);
76
+
77
+ const decryptedBuffer = await crypto.subtle.decrypt(
78
+ { name: 'AES-GCM', iv },
79
+ key,
80
+ encrypted
81
+ );
82
+
83
+ const decoder = new TextDecoder();
84
+ return decoder.decode(decryptedBuffer);
85
+ } catch (error) {
86
+ console.warn('Decryption failed, returning data as-is:', error);
87
+ return encryptedData;
88
+ }
89
+ }
90
+
91
+ // Secure storage wrapper for sensitive data
92
+ export const secureStorage = {
93
+ async setItem(key: string, value: string): Promise<void> {
94
+ const encrypted = await encryptData(value);
95
+ localStorage.setItem(`secure_${key}`, encrypted);
96
+ },
97
+
98
+ async getItem(key: string): Promise<string | null> {
99
+ const encrypted = localStorage.getItem(`secure_${key}`);
100
+ if (!encrypted) return null;
101
+
102
+ return await decryptData(encrypted);
103
+ },
104
+
105
+ removeItem(key: string): void {
106
+ localStorage.removeItem(`secure_${key}`);
107
+ }
108
+ };