mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-09 20:18:50 +01:00
feat: Add OAuth token revocation endpoint and related functionality
This commit is contained in:
parent
f25407768e
commit
8b1d804391
9 changed files with 85 additions and 11 deletions
|
|
@ -399,10 +399,10 @@ async function getServerConnectionStatus(
|
|||
}
|
||||
}
|
||||
|
||||
// return {
|
||||
// requiresOAuth: oauthServers.has(serverName),
|
||||
// connectionState: finalConnectionState,
|
||||
// };
|
||||
return {
|
||||
requiresOAuth,
|
||||
connectionState: finalConnectionState,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -128,11 +128,11 @@ export default function MCPConfigDialog({
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/* Server Initialization Section */}
|
||||
{/* Server Initialization Section - Always show for OAuth servers or when custom vars exist */}
|
||||
<ServerInitializationSection
|
||||
serverName={serverName}
|
||||
requiresOAuth={serverStatus?.requiresOAuth || false}
|
||||
hasCustomUserVars={fieldsSchema && Object.keys(fieldsSchema).length > 0}
|
||||
hasCustomUserVars={hasFields}
|
||||
/>
|
||||
</OGDialogContent>
|
||||
</OGDialog>
|
||||
|
|
|
|||
|
|
@ -73,8 +73,8 @@ export default function MCPServerStatusIcon({
|
|||
}
|
||||
|
||||
if (connectionState === 'connected') {
|
||||
// Only show config button if there are customUserVars to configure
|
||||
if (hasCustomUserVars) {
|
||||
// Show config button if there are customUserVars to configure OR if it's an OAuth server (for revoke functionality)
|
||||
if (hasCustomUserVars || requiresOAuth) {
|
||||
const isAuthenticated = tool?.authenticated || requiresOAuth;
|
||||
return (
|
||||
<AuthenticatedStatusIcon
|
||||
|
|
@ -84,7 +84,7 @@ export default function MCPServerStatusIcon({
|
|||
/>
|
||||
);
|
||||
}
|
||||
return null; // No config button for connected servers without customUserVars
|
||||
return null; // No config button for connected servers without customUserVars or OAuth
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export default function ServerInitializationSection({
|
|||
initializeServer,
|
||||
connectionStatus,
|
||||
cancelOAuthFlow,
|
||||
revokeOAuthFlow,
|
||||
isInitializing,
|
||||
isCancellable,
|
||||
getOAuthUrl,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useCallback, useState, useMemo, useRef, useEffect } from 'react';
|
||||
import { useToastContext } from '@librechat/client';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { Constants, QueryKeys } from 'librechat-data-provider';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { Constants, QueryKeys, dataService } from 'librechat-data-provider';
|
||||
import {
|
||||
useCancelMCPOAuthMutation,
|
||||
useUpdateUserPluginsMutation,
|
||||
|
|
@ -48,6 +48,17 @@ export function useMCPServerManager() {
|
|||
const reinitializeMutation = useReinitializeMCPServerMutation();
|
||||
const cancelOAuthMutation = useCancelMCPOAuthMutation();
|
||||
|
||||
// Create OAuth revoke mutation using the data service (which includes authentication)
|
||||
const revokeOAuthMutation = useMutation(
|
||||
(serverName: string) => dataService.revokeMCPOAuth(serverName),
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.mcpConnectionStatus]);
|
||||
queryClient.invalidateQueries([QueryKeys.tools]);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const updateUserPluginsMutation = useUpdateUserPluginsMutation({
|
||||
onSuccess: async () => {
|
||||
showToast({ message: localize('com_nav_mcp_vars_updated'), status: 'success' });
|
||||
|
|
@ -295,6 +306,38 @@ export function useMCPServerManager() {
|
|||
[queryClient, cleanupServerState, showToast, localize, cancelOAuthMutation],
|
||||
);
|
||||
|
||||
const revokeOAuthFlow = useCallback(
|
||||
(serverName: string) => {
|
||||
revokeOAuthMutation.mutate(serverName, {
|
||||
onSuccess: () => {
|
||||
cleanupServerState(serverName);
|
||||
|
||||
// Remove server from selected values since OAuth tokens are revoked
|
||||
const currentValues = mcpValues ?? [];
|
||||
const filteredValues = currentValues.filter((name) => name !== serverName);
|
||||
setMCPValues(filteredValues);
|
||||
|
||||
// Force refresh of connection status to reflect the revoked state
|
||||
queryClient.invalidateQueries([QueryKeys.mcpConnectionStatus]);
|
||||
queryClient.invalidateQueries([QueryKeys.tools]);
|
||||
|
||||
showToast({
|
||||
message: `OAuth tokens revoked for ${serverName}. You can now re-authenticate.`,
|
||||
status: 'success',
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(`[MCP Manager] Failed to revoke OAuth for ${serverName}:`, error);
|
||||
showToast({
|
||||
message: `Failed to revoke OAuth tokens for ${serverName}`,
|
||||
status: 'error',
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
[revokeOAuthMutation, cleanupServerState, mcpValues, setMCPValues, showToast, queryClient],
|
||||
);
|
||||
|
||||
const isInitializing = useCallback(
|
||||
(serverName: string) => {
|
||||
return serverStates[serverName]?.isInitializing || false;
|
||||
|
|
@ -536,6 +579,7 @@ export function useMCPServerManager() {
|
|||
connectionStatus,
|
||||
initializeServer,
|
||||
cancelOAuthFlow,
|
||||
revokeOAuthFlow,
|
||||
isInitializing,
|
||||
isCancellable,
|
||||
getOAuthUrl,
|
||||
|
|
|
|||
|
|
@ -144,6 +144,10 @@ export const cancelMCPOAuth = (serverName: string) => {
|
|||
return `/api/mcp/oauth/cancel/${serverName}`;
|
||||
};
|
||||
|
||||
export const revokeMCPOAuth = (serverName: string) => {
|
||||
return `/api/mcp/${serverName}/oauth/revoke`;
|
||||
};
|
||||
|
||||
export const config = () => '/api/config';
|
||||
|
||||
export const prompts = () => '/api/prompts';
|
||||
|
|
|
|||
|
|
@ -163,6 +163,10 @@ export function cancelMCPOAuth(serverName: string): Promise<m.CancelMCPOAuthResp
|
|||
return request.post(endpoints.cancelMCPOAuth(serverName), {});
|
||||
}
|
||||
|
||||
export function revokeMCPOAuth(serverName: string): Promise<m.RevokeMCPOAuthResponse> {
|
||||
return request.post(endpoints.revokeMCPOAuth(serverName), {});
|
||||
}
|
||||
|
||||
/* Config */
|
||||
|
||||
export const getStartupConfig = (): Promise<
|
||||
|
|
|
|||
|
|
@ -351,6 +351,21 @@ export const useCancelMCPOAuthMutation = (): UseMutationResult<
|
|||
});
|
||||
};
|
||||
|
||||
export const useRevokeMCPOAuthMutation = (): UseMutationResult<
|
||||
m.RevokeMCPOAuthResponse,
|
||||
unknown,
|
||||
string,
|
||||
unknown
|
||||
> => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation((serverName: string) => dataService.revokeMCPOAuth(serverName), {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.mcpConnectionStatus]);
|
||||
queryClient.invalidateQueries([QueryKeys.tools]);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetCustomConfigSpeechQuery = (
|
||||
config?: UseQueryOptions<t.TCustomConfigSpeechResponse>,
|
||||
): QueryObserverResult<t.TCustomConfigSpeechResponse> => {
|
||||
|
|
|
|||
|
|
@ -378,3 +378,9 @@ export interface CancelMCPOAuthResponse {
|
|||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface RevokeMCPOAuthResponse {
|
||||
success: boolean;
|
||||
message: string;
|
||||
serverName: string;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue