Skip to content

Commit

Permalink
Formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
jspahrsummers committed Jan 24, 2025
1 parent fce6644 commit 0882a3e
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 45 deletions.
6 changes: 3 additions & 3 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ const App = () => {
newUrl.searchParams.delete("serverUrl");
window.history.replaceState({}, "", newUrl.toString());
// Show success toast for OAuth
toast.success('Successfully authenticated with OAuth');
toast.success("Successfully authenticated with OAuth");
// Connect to the server
connectMcpServer();
}
Expand Down Expand Up @@ -446,8 +446,8 @@ const App = () => {

<div className="w-full">
{!serverCapabilities?.resources &&
!serverCapabilities?.prompts &&
!serverCapabilities?.tools ? (
!serverCapabilities?.prompts &&
!serverCapabilities?.tools ? (
<div className="flex items-center justify-center p-4">
<p className="text-lg text-gray-500">
The connected server does not support any MCP capabilities
Expand Down
18 changes: 9 additions & 9 deletions client/src/components/OAuthCallback.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useRef } from 'react';
import { handleOAuthCallback } from '../lib/auth';
import { SESSION_KEYS } from '../lib/constants';
import { useEffect, useRef } from "react";
import { handleOAuthCallback } from "../lib/auth";
import { SESSION_KEYS } from "../lib/constants";

const OAuthCallback = () => {
const hasProcessedRef = useRef(false);
Expand All @@ -14,12 +14,12 @@ const OAuthCallback = () => {
hasProcessedRef.current = true;

const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const code = params.get("code");
const serverUrl = sessionStorage.getItem(SESSION_KEYS.SERVER_URL);

if (!code || !serverUrl) {
console.error('Missing code or server URL');
window.location.href = '/';
console.error("Missing code or server URL");
window.location.href = "/";
return;
}

Expand All @@ -30,8 +30,8 @@ const OAuthCallback = () => {
// Redirect back to the main app with server URL to trigger auto-connect
window.location.href = `/?serverUrl=${encodeURIComponent(serverUrl)}`;
} catch (error) {
console.error('OAuth callback error:', error);
window.location.href = '/';
console.error("OAuth callback error:", error);
window.location.href = "/";
}
};

Expand All @@ -45,4 +45,4 @@ const OAuthCallback = () => {
);
};

export default OAuthCallback;
export default OAuthCallback;
48 changes: 28 additions & 20 deletions client/src/lib/auth.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
import pkceChallenge from 'pkce-challenge';
import { SESSION_KEYS } from './constants';
import pkceChallenge from "pkce-challenge";
import { SESSION_KEYS } from "./constants";

export interface OAuthMetadata {
authorization_endpoint: string;
token_endpoint: string;
}

export async function discoverOAuthMetadata(serverUrl: string): Promise<OAuthMetadata> {
export async function discoverOAuthMetadata(
serverUrl: string,
): Promise<OAuthMetadata> {
try {
const url = new URL('/.well-known/oauth-authorization-server', serverUrl);
const url = new URL("/.well-known/oauth-authorization-server", serverUrl);
const response = await fetch(url.toString());

if (response.ok) {
const metadata = await response.json();
return {
authorization_endpoint: metadata.authorization_endpoint,
token_endpoint: metadata.token_endpoint
token_endpoint: metadata.token_endpoint,
};
}
} catch (error) {
console.warn('OAuth metadata discovery failed:', error);
console.warn("OAuth metadata discovery failed:", error);
}

// Fall back to default endpoints
const baseUrl = new URL(serverUrl);
return {
authorization_endpoint: new URL('/authorize', baseUrl).toString(),
token_endpoint: new URL('/token', baseUrl).toString()
authorization_endpoint: new URL("/authorize", baseUrl).toString(),
token_endpoint: new URL("/token", baseUrl).toString(),
};
}

Expand All @@ -44,40 +46,46 @@ export async function startOAuthFlow(serverUrl: string): Promise<string> {

// Build authorization URL
const authUrl = new URL(metadata.authorization_endpoint);
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('redirect_uri', window.location.origin + '/oauth/callback');
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("code_challenge", codeChallenge);
authUrl.searchParams.set("code_challenge_method", "S256");
authUrl.searchParams.set(
"redirect_uri",
window.location.origin + "/oauth/callback",
);

return authUrl.toString();
}

export async function handleOAuthCallback(serverUrl: string, code: string): Promise<string> {
export async function handleOAuthCallback(
serverUrl: string,
code: string,
): Promise<string> {
// Get stored code verifier
const codeVerifier = sessionStorage.getItem(SESSION_KEYS.CODE_VERIFIER);
if (!codeVerifier) {
throw new Error('No code verifier found');
throw new Error("No code verifier found");
}

// Discover OAuth endpoints
const metadata = await discoverOAuthMetadata(serverUrl);

// Exchange code for tokens
const response = await fetch(metadata.token_endpoint, {
method: 'POST',
method: "POST",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
body: JSON.stringify({
grant_type: 'authorization_code',
grant_type: "authorization_code",
code,
code_verifier: codeVerifier,
redirect_uri: window.location.origin + '/oauth/callback'
})
redirect_uri: window.location.origin + "/oauth/callback",
}),
});

if (!response.ok) {
throw new Error('Token exchange failed');
throw new Error("Token exchange failed");
}

const data = await response.json();
Expand Down
8 changes: 4 additions & 4 deletions client/src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// OAuth-related session storage keys
export const SESSION_KEYS = {
CODE_VERIFIER: 'mcp_code_verifier',
SERVER_URL: 'mcp_server_url',
ACCESS_TOKEN: 'mcp_access_token',
} as const;
CODE_VERIFIER: "mcp_code_verifier",
SERVER_URL: "mcp_server_url",
ACCESS_TOKEN: "mcp_access_token",
} as const;
7 changes: 5 additions & 2 deletions client/src/lib/hooks/useConnection.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport, SseError } from "@modelcontextprotocol/sdk/client/sse.js";
import {
SSEClientTransport,
SseError,
} from "@modelcontextprotocol/sdk/client/sse.js";
import {
ClientNotification,
ClientRequest,
Expand Down Expand Up @@ -149,7 +152,7 @@ export function useConnection({
const headers: HeadersInit = {};
const accessToken = sessionStorage.getItem(SESSION_KEYS.ACCESS_TOKEN);
if (accessToken) {
headers['Authorization'] = `Bearer ${accessToken}`;
headers["Authorization"] = `Bearer ${accessToken}`;
}

const clientTransport = new SSEClientTransport(backendUrl, {
Expand Down
3 changes: 1 addition & 2 deletions client/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
},
server: {},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
Expand Down
15 changes: 10 additions & 5 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import cors from "cors";
import { parseArgs } from "node:util";
import { parse as shellParseArgs } from "shell-quote";

import { SSEClientTransport, SseError } from "@modelcontextprotocol/sdk/client/sse.js";
import {
SSEClientTransport,
SseError,
} from "@modelcontextprotocol/sdk/client/sse.js";
import {
StdioClientTransport,
getDefaultEnvironment,
Expand All @@ -14,7 +17,7 @@ import express from "express";
import { findActualExecutable } from "spawn-rx";
import mcpProxy from "./mcpProxy.js";

const SSE_HEADERS_PASSTHROUGH = ['authorization'];
const SSE_HEADERS_PASSTHROUGH = ["authorization"];

const defaultEnvironment = {
...getDefaultEnvironment(),
Expand Down Expand Up @@ -67,7 +70,6 @@ const createTransport = async (req: express.Request) => {
for (const key of SSE_HEADERS_PASSTHROUGH) {
if (req.headers[key] === undefined) {
continue;

}

const value = req.headers[key];
Expand Down Expand Up @@ -103,7 +105,10 @@ app.get("/sse", async (req, res) => {
backingServerTransport = await createTransport(req);
} catch (error) {
if (error instanceof SseError && error.code === 401) {
console.error("Received 401 Unauthorized from MCP server:", error.message);
console.error(
"Received 401 Unauthorized from MCP server:",
error.message,
);
res.status(401).json(error);
return;
}
Expand Down Expand Up @@ -176,4 +181,4 @@ app.get("/config", (req, res) => {
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => { });
app.listen(PORT, () => {});

0 comments on commit 0882a3e

Please sign in to comment.