mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-17 17:41:24 -07:00
feat(admin): apply MCP allowlist to extensions & gemini mcp list command (#18442)
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
sanitizeAdminSettings,
|
||||
stopAdminControlsPolling,
|
||||
getAdminErrorMessage,
|
||||
getAdminBlockedMcpServersMessage,
|
||||
} from './admin_controls.js';
|
||||
import type { CodeAssistServer } from '../server.js';
|
||||
import type { Config } from '../../config/config.js';
|
||||
@@ -759,4 +760,55 @@ describe('Admin Controls', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAdminBlockedMcpServersMessage', () => {
|
||||
let mockConfig: Config;
|
||||
|
||||
beforeEach(() => {
|
||||
mockConfig = {} as Config;
|
||||
});
|
||||
|
||||
it('should show count for a single blocked server', () => {
|
||||
vi.mocked(getCodeAssistServer).mockReturnValue({
|
||||
projectId: 'test-project-123',
|
||||
} as CodeAssistServer);
|
||||
|
||||
const message = getAdminBlockedMcpServersMessage(
|
||||
['server-1'],
|
||||
mockConfig,
|
||||
);
|
||||
|
||||
expect(message).toBe(
|
||||
'1 MCP server is not allowlisted by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli?project=test-project-123',
|
||||
);
|
||||
});
|
||||
|
||||
it('should show count for multiple blocked servers', () => {
|
||||
vi.mocked(getCodeAssistServer).mockReturnValue({
|
||||
projectId: 'test-project-123',
|
||||
} as CodeAssistServer);
|
||||
|
||||
const message = getAdminBlockedMcpServersMessage(
|
||||
['server-1', 'server-2', 'server-3'],
|
||||
mockConfig,
|
||||
);
|
||||
|
||||
expect(message).toBe(
|
||||
'3 MCP servers are not allowlisted by your administrator. To enable them, please request an update to the settings at: https://goo.gle/manage-gemini-cli?project=test-project-123',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format message correctly with no project ID', () => {
|
||||
vi.mocked(getCodeAssistServer).mockReturnValue(undefined);
|
||||
|
||||
const message = getAdminBlockedMcpServersMessage(
|
||||
['server-1', 'server-2'],
|
||||
mockConfig,
|
||||
);
|
||||
|
||||
expect(message).toBe(
|
||||
'2 MCP servers are not allowlisted by your administrator. To enable them, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -238,3 +238,25 @@ export function getAdminErrorMessage(
|
||||
const projectParam = projectId ? `?project=${projectId}` : '';
|
||||
return `${featureName} is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli${projectParam}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a standardized error message for MCP servers blocked by the admin allowlist.
|
||||
*
|
||||
* @param blockedServers List of blocked server names
|
||||
* @param config The application config
|
||||
* @returns The formatted error message
|
||||
*/
|
||||
export function getAdminBlockedMcpServersMessage(
|
||||
blockedServers: string[],
|
||||
config: Config | undefined,
|
||||
): string {
|
||||
const server = config ? getCodeAssistServer(config) : undefined;
|
||||
const projectId = server?.projectId;
|
||||
const projectParam = projectId ? `?project=${projectId}` : '';
|
||||
const count = blockedServers.length;
|
||||
const serverText = count === 1 ? 'server is' : 'servers are';
|
||||
|
||||
return `${count} MCP ${serverText} not allowlisted by your administrator. To enable ${
|
||||
count === 1 ? 'it' : 'them'
|
||||
}, please request an update to the settings at: https://goo.gle/manage-gemini-cli${projectParam}`;
|
||||
}
|
||||
|
||||
113
packages/core/src/code_assist/admin/mcpUtils.test.ts
Normal file
113
packages/core/src/code_assist/admin/mcpUtils.test.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { applyAdminAllowlist } from './mcpUtils.js';
|
||||
import type { MCPServerConfig } from '../../config/config.js';
|
||||
|
||||
describe('applyAdminAllowlist', () => {
|
||||
it('should return original servers if no allowlist provided', () => {
|
||||
const localServers: Record<string, MCPServerConfig> = {
|
||||
server1: { command: 'cmd1' },
|
||||
};
|
||||
expect(applyAdminAllowlist(localServers, undefined)).toEqual({
|
||||
mcpServers: localServers,
|
||||
blockedServerNames: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return original servers if allowlist is empty', () => {
|
||||
const localServers: Record<string, MCPServerConfig> = {
|
||||
server1: { command: 'cmd1' },
|
||||
};
|
||||
expect(applyAdminAllowlist(localServers, {})).toEqual({
|
||||
mcpServers: localServers,
|
||||
blockedServerNames: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter servers not in allowlist', () => {
|
||||
const localServers: Record<string, MCPServerConfig> = {
|
||||
server1: { command: 'cmd1' },
|
||||
server2: { command: 'cmd2' },
|
||||
};
|
||||
const allowlist: Record<string, MCPServerConfig> = {
|
||||
server1: { url: 'http://server1' },
|
||||
};
|
||||
|
||||
const result = applyAdminAllowlist(localServers, allowlist);
|
||||
expect(Object.keys(result.mcpServers)).toEqual(['server1']);
|
||||
expect(result.blockedServerNames).toEqual(['server2']);
|
||||
});
|
||||
|
||||
it('should override connection details with allowlist values', () => {
|
||||
const localServers: Record<string, MCPServerConfig> = {
|
||||
server1: {
|
||||
command: 'local-cmd',
|
||||
args: ['local-arg'],
|
||||
env: { LOCAL: 'true' },
|
||||
description: 'Local description',
|
||||
},
|
||||
};
|
||||
const allowlist: Record<string, MCPServerConfig> = {
|
||||
server1: {
|
||||
url: 'http://admin-url',
|
||||
type: 'sse',
|
||||
trust: true,
|
||||
},
|
||||
};
|
||||
|
||||
const result = applyAdminAllowlist(localServers, allowlist);
|
||||
const server = result.mcpServers['server1'];
|
||||
|
||||
expect(server).toBeDefined();
|
||||
expect(server?.url).toBe('http://admin-url');
|
||||
expect(server?.type).toBe('sse');
|
||||
expect(server?.trust).toBe(true);
|
||||
// Should preserve other local fields
|
||||
expect(server?.description).toBe('Local description');
|
||||
// Should remove local connection fields
|
||||
expect(server?.command).toBeUndefined();
|
||||
expect(server?.args).toBeUndefined();
|
||||
expect(server?.env).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should apply tool restrictions from allowlist', () => {
|
||||
const localServers: Record<string, MCPServerConfig> = {
|
||||
server1: { command: 'cmd1' },
|
||||
};
|
||||
const allowlist: Record<string, MCPServerConfig> = {
|
||||
server1: {
|
||||
url: 'http://url',
|
||||
includeTools: ['tool1'],
|
||||
excludeTools: ['tool2'],
|
||||
},
|
||||
};
|
||||
|
||||
const result = applyAdminAllowlist(localServers, allowlist);
|
||||
expect(result.mcpServers['server1']?.includeTools).toEqual(['tool1']);
|
||||
expect(result.mcpServers['server1']?.excludeTools).toEqual(['tool2']);
|
||||
});
|
||||
|
||||
it('should not apply empty tool restrictions from allowlist', () => {
|
||||
const localServers: Record<string, MCPServerConfig> = {
|
||||
server1: {
|
||||
command: 'cmd1',
|
||||
includeTools: ['local-tool'],
|
||||
},
|
||||
};
|
||||
const allowlist: Record<string, MCPServerConfig> = {
|
||||
server1: {
|
||||
url: 'http://url',
|
||||
includeTools: [],
|
||||
},
|
||||
};
|
||||
|
||||
const result = applyAdminAllowlist(localServers, allowlist);
|
||||
// Should keep local tool restrictions if admin ones are empty/undefined
|
||||
expect(result.mcpServers['server1']?.includeTools).toEqual(['local-tool']);
|
||||
});
|
||||
});
|
||||
67
packages/core/src/code_assist/admin/mcpUtils.ts
Normal file
67
packages/core/src/code_assist/admin/mcpUtils.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { MCPServerConfig } from '../../config/config.js';
|
||||
|
||||
/**
|
||||
* Applies the admin allowlist to the local MCP servers.
|
||||
*
|
||||
* If an admin allowlist is provided and not empty, this function filters the
|
||||
* local servers to only those present in the allowlist. It also overrides
|
||||
* connection details (url, type, trust) with the admin configuration and
|
||||
* removes local execution details (command, args, env, cwd).
|
||||
*
|
||||
* @param localMcpServers The locally configured MCP servers.
|
||||
* @param adminAllowlist The admin allowlist configuration.
|
||||
* @returns The filtered and merged MCP servers.
|
||||
*/
|
||||
export function applyAdminAllowlist(
|
||||
localMcpServers: Record<string, MCPServerConfig>,
|
||||
adminAllowlist: Record<string, MCPServerConfig> | undefined,
|
||||
): {
|
||||
mcpServers: Record<string, MCPServerConfig>;
|
||||
blockedServerNames: string[];
|
||||
} {
|
||||
if (!adminAllowlist || Object.keys(adminAllowlist).length === 0) {
|
||||
return { mcpServers: localMcpServers, blockedServerNames: [] };
|
||||
}
|
||||
|
||||
const filteredMcpServers: Record<string, MCPServerConfig> = {};
|
||||
const blockedServerNames: string[] = [];
|
||||
|
||||
for (const [serverId, localConfig] of Object.entries(localMcpServers)) {
|
||||
const adminConfig = adminAllowlist[serverId];
|
||||
if (adminConfig) {
|
||||
const mergedConfig = {
|
||||
...localConfig,
|
||||
url: adminConfig.url,
|
||||
type: adminConfig.type,
|
||||
trust: adminConfig.trust,
|
||||
};
|
||||
|
||||
// Remove local connection details
|
||||
delete mergedConfig.command;
|
||||
delete mergedConfig.args;
|
||||
delete mergedConfig.env;
|
||||
delete mergedConfig.cwd;
|
||||
delete mergedConfig.httpUrl;
|
||||
delete mergedConfig.tcp;
|
||||
|
||||
if (
|
||||
(adminConfig.includeTools && adminConfig.includeTools.length > 0) ||
|
||||
(adminConfig.excludeTools && adminConfig.excludeTools.length > 0)
|
||||
) {
|
||||
mergedConfig.includeTools = adminConfig.includeTools;
|
||||
mergedConfig.excludeTools = adminConfig.excludeTools;
|
||||
}
|
||||
|
||||
filteredMcpServers[serverId] = mergedConfig;
|
||||
} else {
|
||||
blockedServerNames.push(serverId);
|
||||
}
|
||||
}
|
||||
return { mcpServers: filteredMcpServers, blockedServerNames };
|
||||
}
|
||||
Reference in New Issue
Block a user