Add support for MCP server instructions behind config option (#13432)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
christine betts
2025-11-26 13:08:47 -05:00
committed by GitHub
parent 558c8ece2c
commit bc365f1eaa
9 changed files with 129 additions and 1 deletions

View File

@@ -156,6 +156,8 @@ Each server configuration supports the following properties:
- **`targetServiceAccount`** (string): The email address of the Google Cloud
Service Account to impersonate. Used with
`authProviderType: 'service_account_impersonation'`.
- **`useInstructions`** (boolean): If true will include the MCP server's
initialization instructions in the system instructions.
### OAuth Support for Remote MCP Servers

View File

@@ -1516,6 +1516,11 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record<
description:
'Service account email to impersonate (name@project.iam.gserviceaccount.com).',
},
useInstructions: {
type: 'boolean',
description:
'If true, instructions from this server will be included in the system prompt.',
},
},
},
TelemetrySettings: {

View File

@@ -203,6 +203,8 @@ export class MCPServerConfig {
readonly targetAudience?: string,
/* targetServiceAccount format: <service-account-name>@<project-num>.iam.gserviceaccount.com */
readonly targetServiceAccount?: string,
// Include the MCP server initialization instructions in the system instructions
readonly useInstructions?: boolean,
) {}
}

View File

@@ -193,4 +193,55 @@ describe('McpClientManager', () => {
);
});
});
describe('getMcpInstructions', () => {
it('should only return instructions from servers with useInstructions: true', async () => {
// Override McpClient mock for this test to return distinct objects based on config
vi.mocked(McpClient).mockImplementation(
(name, config) =>
({
connect: vi.fn(),
discover: vi.fn(),
disconnect: vi.fn(),
getServerConfig: vi.fn().mockReturnValue(config),
getInstructions: vi
.fn()
.mockReturnValue(`Instructions for ${name}`),
}) as unknown as McpClient,
);
const manager = new McpClientManager({} as ToolRegistry, mockConfig);
// 1. Configured server with useInstructions: true
mockConfig.getMcpServers.mockReturnValue({
'enabled-server': {
useInstructions: true,
},
'disabled-server': {
useInstructions: false,
},
'default-server': {
// undefined should be treated as false
},
});
await manager.startConfiguredMcpServers();
const instructions = manager.getMcpInstructions();
expect(instructions).toContain(
"# Instructions for MCP Server 'enabled-server'",
);
expect(instructions).toContain('Instructions for enabled-server');
expect(instructions).not.toContain(
"# Instructions for MCP Server 'disabled-server'",
);
expect(instructions).not.toContain('Instructions for disabled-server');
expect(instructions).not.toContain(
"# Instructions for MCP Server 'default-server'",
);
expect(instructions).not.toContain('Instructions for default-server');
});
});
});

View File

@@ -323,4 +323,20 @@ export class McpClientManager {
}
return mcpServers;
}
getMcpInstructions(): string {
const instructions: string[] = [];
for (const [name, client] of this.clients) {
// Only include instructions if explicitly enabled in config
if (client.getServerConfig().useInstructions) {
const clientInstructions = client.getInstructions();
if (clientInstructions) {
instructions.push(
`# Instructions for MCP Server '${name}'\n${clientInstructions}`,
);
}
}
}
return instructions.join('\n\n');
}
}

View File

@@ -218,6 +218,10 @@ export class McpClient {
getServerConfig(): MCPServerConfig {
return this.serverConfig;
}
getInstructions(): string | undefined {
return this.client?.getInstructions();
}
}
/**

View File

@@ -934,4 +934,43 @@ included directory memory
expect(config.getGeminiMdFilePaths()).equals(refreshResult.filePaths);
expect(mockEventListener).toHaveBeenCalledExactlyOnceWith(refreshResult);
});
it('should include MCP instructions in user memory', async () => {
const mockConfig = {
getWorkingDir: vi.fn().mockReturnValue(cwd),
shouldLoadMemoryFromIncludeDirectories: vi.fn().mockReturnValue(false),
getDebugMode: vi.fn().mockReturnValue(false),
getFileService: vi
.fn()
.mockReturnValue(new FileDiscoveryService(projectRoot)),
getExtensionLoader: vi
.fn()
.mockReturnValue(new SimpleExtensionLoader([])),
isTrustedFolder: vi.fn().mockReturnValue(true),
getImportFormat: vi.fn().mockReturnValue('tree'),
getFileFilteringOptions: vi.fn().mockReturnValue(undefined),
getDiscoveryMaxDirs: vi.fn().mockReturnValue(200),
setUserMemory: vi.fn(),
setGeminiMdFileCount: vi.fn(),
setGeminiMdFilePaths: vi.fn(),
getMcpClientManager: vi.fn().mockReturnValue({
getMcpInstructions: vi
.fn()
.mockReturnValue(
"\n\n# Instructions for MCP Server 'extension-server'\nAlways be polite.",
),
}),
} as unknown as Config;
await refreshServerHierarchicalMemory(mockConfig);
expect(mockConfig.setUserMemory).toHaveBeenCalledWith(
expect.stringContaining(
"# Instructions for MCP Server 'extension-server'",
),
);
expect(mockConfig.setUserMemory).toHaveBeenCalledWith(
expect.stringContaining('Always be polite.'),
);
});
});

View File

@@ -569,7 +569,12 @@ export async function refreshServerHierarchicalMemory(config: Config) {
config.getFileFilteringOptions(),
config.getDiscoveryMaxDirs(),
);
config.setUserMemory(result.memoryContent);
const mcpInstructions =
config.getMcpClientManager()?.getMcpInstructions() || '';
const finalMemory = [result.memoryContent, mcpInstructions.trimStart()]
.filter(Boolean)
.join('\n\n');
config.setUserMemory(finalMemory);
config.setGeminiMdFileCount(result.fileCount);
config.setGeminiMdFilePaths(result.filePaths);
coreEvents.emit(CoreEvent.MemoryChanged, result);

View File

@@ -1460,6 +1460,10 @@
"targetServiceAccount": {
"type": "string",
"description": "Service account email to impersonate (name@project.iam.gserviceaccount.com)."
},
"useInstructions": {
"type": "boolean",
"description": "If true, instructions from this server will be included in the system prompt."
}
}
},