🛂 fix: Gate MCP Queries Behind USE Permission to Prevent 403 Spam (#12345)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run

* 🐛 fix: Gate MCP queries behind USE permission to prevent 403 spam

Closes #12342

When `interface.mcpServers.use` is set to `false` in `librechat.yaml`,
the frontend was still unconditionally fetching `/api/mcp/servers` on
every app startup, window focus, and stale interval — producing
continuous 403 "Insufficient permissions" log entries.

Add `useHasAccess` permission checks to both `useMCPServersQuery` call
sites (`useAppStartup` and `useMCPServerManager`) so the query is
disabled when the user lacks `MCP_SERVERS.USE`, matching the guard
pattern already used by MCP UI components.

* fix: Lint and import order corrections

* fix: Address review findings — gate permissions query, add tests

- Gate `useGetAllEffectivePermissionsQuery` behind `canUseMcp` in
  `useMCPServerManager` for consistency (wasted request when MCP
  disabled, even though this endpoint doesn't 403)
- Sort multi-line `librechat-data-provider` import shortest to longest
- Restore intent comment on `useGetStartupConfig` call
- Add `useAppStartup` test suite covering MCP permission gating:
  query suppression when USE denied, compound `enabled` conditions
  for tools query (servers loading, empty, no user)
This commit is contained in:
Danny Avila 2026-03-20 17:10:39 -04:00 committed by GitHub
parent 365a0dc0f6
commit 01f19b503a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 158 additions and 9 deletions

View file

@ -2,7 +2,14 @@ import { useCallback, useState, useMemo, useRef, useEffect } from 'react';
import { useAtom } from 'jotai';
import { useToastContext } from '@librechat/client';
import { useQueryClient } from '@tanstack/react-query';
import { Constants, QueryKeys, MCPOptions, ResourceType } from 'librechat-data-provider';
import {
Constants,
QueryKeys,
MCPOptions,
Permissions,
ResourceType,
PermissionTypes,
} from 'librechat-data-provider';
import {
useCancelMCPOAuthMutation,
useUpdateUserPluginsMutation,
@ -11,7 +18,7 @@ import {
} from 'librechat-data-provider/react-query';
import type { TUpdateUserPlugins, TPlugin, MCPServersResponse } from 'librechat-data-provider';
import type { ConfigFieldDetail } from '~/common';
import { useLocalize, useMCPSelect, useMCPConnectionStatus } from '~/hooks';
import { useLocalize, useHasAccess, useMCPSelect, useMCPConnectionStatus } from '~/hooks';
import { useGetStartupConfig, useMCPServersQuery } from '~/data-provider';
import { mcpServerInitStatesAtom, getServerInitState } from '~/store/mcp';
import type { MCPServerInitState } from '~/store/mcp';
@ -35,12 +42,19 @@ export function useMCPServerManager({
const localize = useLocalize();
const queryClient = useQueryClient();
const { showToast } = useToastContext();
const { data: startupConfig } = useGetStartupConfig(); // Keep for UI config only
/** Retained for `interface.mcpServers.placeholder` used by `placeholderText` below */
const { data: startupConfig } = useGetStartupConfig();
const canUseMcp = useHasAccess({
permissionType: PermissionTypes.MCP_SERVERS,
permission: Permissions.USE,
});
const { data: loadedServers, isLoading } = useMCPServersQuery();
const { data: loadedServers, isLoading } = useMCPServersQuery({ enabled: canUseMcp });
// Fetch effective permissions for all MCP servers
const { data: permissionsMap } = useGetAllEffectivePermissionsQuery(ResourceType.MCPSERVER);
const { data: permissionsMap } = useGetAllEffectivePermissionsQuery(ResourceType.MCPSERVER, {
enabled: canUseMcp,
});
const [isConfigModalOpen, setIsConfigModalOpen] = useState(false);
const [selectedToolForConfig, setSelectedToolForConfig] = useState<TPlugin | null>(null);