🗝️ feat: Credential Variables for DB-Sourced MCP Servers (#12044)
* feat: Allow Credential Variables in Headers for DB-sourced MCP Servers
- Removed the hasCustomUserVars check from ToolService.js, directly retrieving userMCPAuthMap.
- Updated MCPConnectionFactory and related classes to include a dbSourced flag for better handling of database-sourced configurations.
- Added integration tests to ensure proper behavior of dbSourced servers, verifying that sensitive placeholders are not resolved while allowing customUserVars.
- Adjusted various MCP-related files to accommodate the new dbSourced logic, ensuring consistent handling across the codebase.
* chore: MCPConnectionFactory Tests with Additional Flow Metadata for typing
- Updated MCPConnectionFactory tests to include new fields in flowMetadata: serverUrl and state.
- Enhanced mockFlowData in multiple test cases to reflect the updated structure, ensuring comprehensive coverage of the OAuth flow scenarios.
- Added authorization_endpoint to metadata in the test setup for improved validation of the OAuth process.
* refactor: Simplify MCPManager Configuration Handling
- Removed unnecessary type assertions and streamlined the retrieval of server configuration in MCPManager.
- Enhanced the handling of OAuth and database-sourced flags for improved clarity and efficiency.
- Updated tests to reflect changes in user object structure and ensure proper processing of MCP environment variables.
* refactor: Optimize User MCP Auth Map Retrieval in ToolService
- Introduced conditional loading of userMCPAuthMap based on the presence of MCP-delimited tools, improving efficiency by avoiding unnecessary calls.
- Updated the loadToolDefinitionsWrapper and loadAgentTools functions to reflect this change, enhancing overall performance and clarity.
* test: Add userMCPAuthMap gating tests in ToolService
- Introduced new tests to validate the logic for determining if MCP tools are present in the agent's tool list.
- Implemented various scenarios to ensure accurate detection of MCP tools, including edge cases for empty, undefined, and null tool lists.
- Enhanced clarity and coverage of the ToolService capability checking logic.
* refactor: Enhance MCP Environment Variable Processing
- Simplified the handling of the dbSourced parameter in the processMCPEnv function.
- Introduced a failsafe mechanism to derive dbSourced from options if not explicitly provided, improving robustness and clarity in MCP environment variable processing.
* refactor: Update Regex Patterns for Credential Placeholders in ServerConfigsDB
- Modified regex patterns to include additional credential/env placeholders that should not be allowed in user-provided configurations.
- Clarified comments to emphasize the security risks associated with credential exfiltration when MCP servers are shared between users.
* chore: field order
* refactor: Clean Up dbSourced Parameter Handling in processMCPEnv
- Reintroduced the failsafe mechanism for deriving the dbSourced parameter from options, ensuring clarity and robustness in MCP environment variable processing.
- Enhanced code readability by maintaining consistent comment structure.
* refactor: Update MCPOptions Type to Include Optional dbId
- Modified the processMCPEnv function to extend the MCPOptions type, allowing for an optional dbId property.
- Simplified the logic for deriving the dbSourced parameter by directly checking the dbId property, enhancing code clarity and maintainability.
2026-03-03 18:02:37 -05:00
|
|
|
const {
|
🛂 fix: Enforce Actions Capability Gate Across All Event-Driven Tool Loading Paths (#12252)
* fix: gate action tools by actions capability in all code paths
Extract resolveAgentCapabilities helper to eliminate 3x-duplicated
capability resolution. Apply early action-tool filtering in both
loadToolDefinitionsWrapper and loadAgentTools non-definitions path.
Gate loadActionToolsForExecution in loadToolsForExecution behind an
actionsEnabled parameter with a cache-based fallback. Replace the
late capability guard in loadAgentTools with a hasActionTools check
to avoid unnecessary loadActionSets DB calls and duplicate warnings.
* fix: thread actionsEnabled through InitializedAgent type
Add actionsEnabled to the loadTools callback return type,
InitializedAgent, and the initializeAgent destructuring/return
so callers can forward the resolved value to loadToolsForExecution
without redundant getEndpointsConfig cache lookups.
* fix: pass actionsEnabled from callers to loadToolsForExecution
Thread actionsEnabled through the agentToolContexts map in
initialize.js (primary and handoff agents) and through
primaryConfig in the openai.js and responses.js controllers,
avoiding per-tool-call capability re-resolution on the hot path.
* test: add regression tests for action capability gating
Test the real exported functions (resolveAgentCapabilities,
loadAgentTools, loadToolsForExecution) with mocked dependencies
instead of shadow re-implementations. Covers definition filtering,
execution gating, actionsEnabled param forwarding, and fallback
capability resolution.
* test: use Constants.EPHEMERAL_AGENT_ID in ephemeral fallback test
Replaces a string guess with the canonical constant to avoid
fragility if the ephemeral detection heuristic changes.
* fix: populate agentToolContexts for addedConvo parallel agents
After processAddedConvo returns, backfill agentToolContexts for
any agents in agentConfigs not already present, so ON_TOOL_EXECUTE
for added-convo agents receives actionsEnabled instead of falling
back to a per-call cache lookup.
2026-03-15 23:01:36 -04:00
|
|
|
Tools,
|
🗝️ feat: Credential Variables for DB-Sourced MCP Servers (#12044)
* feat: Allow Credential Variables in Headers for DB-sourced MCP Servers
- Removed the hasCustomUserVars check from ToolService.js, directly retrieving userMCPAuthMap.
- Updated MCPConnectionFactory and related classes to include a dbSourced flag for better handling of database-sourced configurations.
- Added integration tests to ensure proper behavior of dbSourced servers, verifying that sensitive placeholders are not resolved while allowing customUserVars.
- Adjusted various MCP-related files to accommodate the new dbSourced logic, ensuring consistent handling across the codebase.
* chore: MCPConnectionFactory Tests with Additional Flow Metadata for typing
- Updated MCPConnectionFactory tests to include new fields in flowMetadata: serverUrl and state.
- Enhanced mockFlowData in multiple test cases to reflect the updated structure, ensuring comprehensive coverage of the OAuth flow scenarios.
- Added authorization_endpoint to metadata in the test setup for improved validation of the OAuth process.
* refactor: Simplify MCPManager Configuration Handling
- Removed unnecessary type assertions and streamlined the retrieval of server configuration in MCPManager.
- Enhanced the handling of OAuth and database-sourced flags for improved clarity and efficiency.
- Updated tests to reflect changes in user object structure and ensure proper processing of MCP environment variables.
* refactor: Optimize User MCP Auth Map Retrieval in ToolService
- Introduced conditional loading of userMCPAuthMap based on the presence of MCP-delimited tools, improving efficiency by avoiding unnecessary calls.
- Updated the loadToolDefinitionsWrapper and loadAgentTools functions to reflect this change, enhancing overall performance and clarity.
* test: Add userMCPAuthMap gating tests in ToolService
- Introduced new tests to validate the logic for determining if MCP tools are present in the agent's tool list.
- Implemented various scenarios to ensure accurate detection of MCP tools, including edge cases for empty, undefined, and null tool lists.
- Enhanced clarity and coverage of the ToolService capability checking logic.
* refactor: Enhance MCP Environment Variable Processing
- Simplified the handling of the dbSourced parameter in the processMCPEnv function.
- Introduced a failsafe mechanism to derive dbSourced from options if not explicitly provided, improving robustness and clarity in MCP environment variable processing.
* refactor: Update Regex Patterns for Credential Placeholders in ServerConfigsDB
- Modified regex patterns to include additional credential/env placeholders that should not be allowed in user-provided configurations.
- Clarified comments to emphasize the security risks associated with credential exfiltration when MCP servers are shared between users.
* chore: field order
* refactor: Clean Up dbSourced Parameter Handling in processMCPEnv
- Reintroduced the failsafe mechanism for deriving the dbSourced parameter from options, ensuring clarity and robustness in MCP environment variable processing.
- Enhanced code readability by maintaining consistent comment structure.
* refactor: Update MCPOptions Type to Include Optional dbId
- Modified the processMCPEnv function to extend the MCPOptions type, allowing for an optional dbId property.
- Simplified the logic for deriving the dbSourced parameter by directly checking the dbId property, enhancing code clarity and maintainability.
2026-03-03 18:02:37 -05:00
|
|
|
Constants,
|
🛂 fix: Enforce Actions Capability Gate Across All Event-Driven Tool Loading Paths (#12252)
* fix: gate action tools by actions capability in all code paths
Extract resolveAgentCapabilities helper to eliminate 3x-duplicated
capability resolution. Apply early action-tool filtering in both
loadToolDefinitionsWrapper and loadAgentTools non-definitions path.
Gate loadActionToolsForExecution in loadToolsForExecution behind an
actionsEnabled parameter with a cache-based fallback. Replace the
late capability guard in loadAgentTools with a hasActionTools check
to avoid unnecessary loadActionSets DB calls and duplicate warnings.
* fix: thread actionsEnabled through InitializedAgent type
Add actionsEnabled to the loadTools callback return type,
InitializedAgent, and the initializeAgent destructuring/return
so callers can forward the resolved value to loadToolsForExecution
without redundant getEndpointsConfig cache lookups.
* fix: pass actionsEnabled from callers to loadToolsForExecution
Thread actionsEnabled through the agentToolContexts map in
initialize.js (primary and handoff agents) and through
primaryConfig in the openai.js and responses.js controllers,
avoiding per-tool-call capability re-resolution on the hot path.
* test: add regression tests for action capability gating
Test the real exported functions (resolveAgentCapabilities,
loadAgentTools, loadToolsForExecution) with mocked dependencies
instead of shadow re-implementations. Covers definition filtering,
execution gating, actionsEnabled param forwarding, and fallback
capability resolution.
* test: use Constants.EPHEMERAL_AGENT_ID in ephemeral fallback test
Replaces a string guess with the canonical constant to avoid
fragility if the ephemeral detection heuristic changes.
* fix: populate agentToolContexts for addedConvo parallel agents
After processAddedConvo returns, backfill agentToolContexts for
any agents in agentConfigs not already present, so ON_TOOL_EXECUTE
for added-convo agents receives actionsEnabled instead of falling
back to a per-call cache lookup.
2026-03-15 23:01:36 -04:00
|
|
|
EModelEndpoint,
|
|
|
|
|
actionDelimiter,
|
🗝️ feat: Credential Variables for DB-Sourced MCP Servers (#12044)
* feat: Allow Credential Variables in Headers for DB-sourced MCP Servers
- Removed the hasCustomUserVars check from ToolService.js, directly retrieving userMCPAuthMap.
- Updated MCPConnectionFactory and related classes to include a dbSourced flag for better handling of database-sourced configurations.
- Added integration tests to ensure proper behavior of dbSourced servers, verifying that sensitive placeholders are not resolved while allowing customUserVars.
- Adjusted various MCP-related files to accommodate the new dbSourced logic, ensuring consistent handling across the codebase.
* chore: MCPConnectionFactory Tests with Additional Flow Metadata for typing
- Updated MCPConnectionFactory tests to include new fields in flowMetadata: serverUrl and state.
- Enhanced mockFlowData in multiple test cases to reflect the updated structure, ensuring comprehensive coverage of the OAuth flow scenarios.
- Added authorization_endpoint to metadata in the test setup for improved validation of the OAuth process.
* refactor: Simplify MCPManager Configuration Handling
- Removed unnecessary type assertions and streamlined the retrieval of server configuration in MCPManager.
- Enhanced the handling of OAuth and database-sourced flags for improved clarity and efficiency.
- Updated tests to reflect changes in user object structure and ensure proper processing of MCP environment variables.
* refactor: Optimize User MCP Auth Map Retrieval in ToolService
- Introduced conditional loading of userMCPAuthMap based on the presence of MCP-delimited tools, improving efficiency by avoiding unnecessary calls.
- Updated the loadToolDefinitionsWrapper and loadAgentTools functions to reflect this change, enhancing overall performance and clarity.
* test: Add userMCPAuthMap gating tests in ToolService
- Introduced new tests to validate the logic for determining if MCP tools are present in the agent's tool list.
- Implemented various scenarios to ensure accurate detection of MCP tools, including edge cases for empty, undefined, and null tool lists.
- Enhanced clarity and coverage of the ToolService capability checking logic.
* refactor: Enhance MCP Environment Variable Processing
- Simplified the handling of the dbSourced parameter in the processMCPEnv function.
- Introduced a failsafe mechanism to derive dbSourced from options if not explicitly provided, improving robustness and clarity in MCP environment variable processing.
* refactor: Update Regex Patterns for Credential Placeholders in ServerConfigsDB
- Modified regex patterns to include additional credential/env placeholders that should not be allowed in user-provided configurations.
- Clarified comments to emphasize the security risks associated with credential exfiltration when MCP servers are shared between users.
* chore: field order
* refactor: Clean Up dbSourced Parameter Handling in processMCPEnv
- Reintroduced the failsafe mechanism for deriving the dbSourced parameter from options, ensuring clarity and robustness in MCP environment variable processing.
- Enhanced code readability by maintaining consistent comment structure.
* refactor: Update MCPOptions Type to Include Optional dbId
- Modified the processMCPEnv function to extend the MCPOptions type, allowing for an optional dbId property.
- Simplified the logic for deriving the dbSourced parameter by directly checking the dbId property, enhancing code clarity and maintainability.
2026-03-03 18:02:37 -05:00
|
|
|
AgentCapabilities,
|
|
|
|
|
defaultAgentCapabilities,
|
|
|
|
|
} = require('librechat-data-provider');
|
2026-01-10 20:25:34 -05:00
|
|
|
|
🛂 fix: Enforce Actions Capability Gate Across All Event-Driven Tool Loading Paths (#12252)
* fix: gate action tools by actions capability in all code paths
Extract resolveAgentCapabilities helper to eliminate 3x-duplicated
capability resolution. Apply early action-tool filtering in both
loadToolDefinitionsWrapper and loadAgentTools non-definitions path.
Gate loadActionToolsForExecution in loadToolsForExecution behind an
actionsEnabled parameter with a cache-based fallback. Replace the
late capability guard in loadAgentTools with a hasActionTools check
to avoid unnecessary loadActionSets DB calls and duplicate warnings.
* fix: thread actionsEnabled through InitializedAgent type
Add actionsEnabled to the loadTools callback return type,
InitializedAgent, and the initializeAgent destructuring/return
so callers can forward the resolved value to loadToolsForExecution
without redundant getEndpointsConfig cache lookups.
* fix: pass actionsEnabled from callers to loadToolsForExecution
Thread actionsEnabled through the agentToolContexts map in
initialize.js (primary and handoff agents) and through
primaryConfig in the openai.js and responses.js controllers,
avoiding per-tool-call capability re-resolution on the hot path.
* test: add regression tests for action capability gating
Test the real exported functions (resolveAgentCapabilities,
loadAgentTools, loadToolsForExecution) with mocked dependencies
instead of shadow re-implementations. Covers definition filtering,
execution gating, actionsEnabled param forwarding, and fallback
capability resolution.
* test: use Constants.EPHEMERAL_AGENT_ID in ephemeral fallback test
Replaces a string guess with the canonical constant to avoid
fragility if the ephemeral detection heuristic changes.
* fix: populate agentToolContexts for addedConvo parallel agents
After processAddedConvo returns, backfill agentToolContexts for
any agents in agentConfigs not already present, so ON_TOOL_EXECUTE
for added-convo agents receives actionsEnabled instead of falling
back to a per-call cache lookup.
2026-03-15 23:01:36 -04:00
|
|
|
const mockGetEndpointsConfig = jest.fn();
|
|
|
|
|
const mockGetMCPServerTools = jest.fn();
|
|
|
|
|
const mockGetCachedTools = jest.fn();
|
|
|
|
|
jest.mock('~/server/services/Config', () => ({
|
|
|
|
|
getEndpointsConfig: (...args) => mockGetEndpointsConfig(...args),
|
|
|
|
|
getMCPServerTools: (...args) => mockGetMCPServerTools(...args),
|
|
|
|
|
getCachedTools: (...args) => mockGetCachedTools(...args),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const mockLoadToolDefinitions = jest.fn();
|
|
|
|
|
const mockGetUserMCPAuthMap = jest.fn();
|
|
|
|
|
jest.mock('@librechat/api', () => ({
|
|
|
|
|
...jest.requireActual('@librechat/api'),
|
|
|
|
|
loadToolDefinitions: (...args) => mockLoadToolDefinitions(...args),
|
|
|
|
|
getUserMCPAuthMap: (...args) => mockGetUserMCPAuthMap(...args),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const mockLoadToolsUtil = jest.fn();
|
|
|
|
|
jest.mock('~/app/clients/tools/util', () => ({
|
|
|
|
|
loadTools: (...args) => mockLoadToolsUtil(...args),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const mockLoadActionSets = jest.fn();
|
|
|
|
|
jest.mock('~/server/services/Tools/credentials', () => ({
|
|
|
|
|
loadAuthValues: jest.fn().mockResolvedValue({}),
|
|
|
|
|
}));
|
|
|
|
|
jest.mock('~/server/services/Tools/search', () => ({
|
|
|
|
|
createOnSearchResults: jest.fn(),
|
|
|
|
|
}));
|
|
|
|
|
jest.mock('~/server/services/Tools/mcp', () => ({
|
|
|
|
|
reinitMCPServer: jest.fn(),
|
|
|
|
|
}));
|
|
|
|
|
jest.mock('~/server/services/Files/process', () => ({
|
|
|
|
|
processFileURL: jest.fn(),
|
|
|
|
|
uploadImageBuffer: jest.fn(),
|
|
|
|
|
}));
|
|
|
|
|
jest.mock('~/app/clients/tools/util/fileSearch', () => ({
|
|
|
|
|
primeFiles: jest.fn().mockResolvedValue({}),
|
|
|
|
|
}));
|
|
|
|
|
jest.mock('~/server/services/Files/Code/process', () => ({
|
|
|
|
|
primeFiles: jest.fn().mockResolvedValue({}),
|
|
|
|
|
}));
|
|
|
|
|
jest.mock('../ActionService', () => ({
|
|
|
|
|
loadActionSets: (...args) => mockLoadActionSets(...args),
|
|
|
|
|
decryptMetadata: jest.fn(),
|
|
|
|
|
createActionTool: jest.fn(),
|
|
|
|
|
domainParser: jest.fn(),
|
|
|
|
|
}));
|
|
|
|
|
jest.mock('~/server/services/Threads', () => ({
|
|
|
|
|
recordUsage: jest.fn(),
|
|
|
|
|
}));
|
|
|
|
|
jest.mock('~/models', () => ({
|
|
|
|
|
findPluginAuthsByKeys: jest.fn(),
|
|
|
|
|
}));
|
|
|
|
|
jest.mock('~/config', () => ({
|
|
|
|
|
getFlowStateManager: jest.fn(() => ({})),
|
|
|
|
|
}));
|
|
|
|
|
jest.mock('~/cache', () => ({
|
|
|
|
|
getLogStores: jest.fn(() => ({})),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
loadAgentTools,
|
|
|
|
|
loadToolsForExecution,
|
|
|
|
|
resolveAgentCapabilities,
|
|
|
|
|
} = require('../ToolService');
|
|
|
|
|
|
|
|
|
|
function createMockReq(capabilities) {
|
|
|
|
|
return {
|
|
|
|
|
user: { id: 'user_123' },
|
|
|
|
|
config: {
|
|
|
|
|
endpoints: {
|
|
|
|
|
[EModelEndpoint.agents]: {
|
|
|
|
|
capabilities,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createEndpointsConfig(capabilities) {
|
|
|
|
|
return {
|
|
|
|
|
[EModelEndpoint.agents]: { capabilities },
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('ToolService - Action Capability Gating', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
jest.clearAllMocks();
|
|
|
|
|
mockLoadToolDefinitions.mockResolvedValue({
|
|
|
|
|
toolDefinitions: [],
|
|
|
|
|
toolRegistry: new Map(),
|
|
|
|
|
hasDeferredTools: false,
|
|
|
|
|
});
|
|
|
|
|
mockLoadToolsUtil.mockResolvedValue({ loadedTools: [], toolContextMap: {} });
|
|
|
|
|
mockLoadActionSets.mockResolvedValue([]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('resolveAgentCapabilities', () => {
|
|
|
|
|
it('should return capabilities from endpoints config', async () => {
|
|
|
|
|
const capabilities = [AgentCapabilities.tools, AgentCapabilities.actions];
|
|
|
|
|
const req = createMockReq(capabilities);
|
|
|
|
|
mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities));
|
|
|
|
|
|
|
|
|
|
const result = await resolveAgentCapabilities(req, req.config, 'agent_123');
|
|
|
|
|
|
|
|
|
|
expect(result).toBeInstanceOf(Set);
|
|
|
|
|
expect(result.has(AgentCapabilities.tools)).toBe(true);
|
|
|
|
|
expect(result.has(AgentCapabilities.actions)).toBe(true);
|
|
|
|
|
expect(result.has(AgentCapabilities.web_search)).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should fall back to default capabilities for ephemeral agents with empty config', async () => {
|
|
|
|
|
const req = createMockReq(defaultAgentCapabilities);
|
|
|
|
|
mockGetEndpointsConfig.mockResolvedValue({});
|
|
|
|
|
|
|
|
|
|
const result = await resolveAgentCapabilities(req, req.config, Constants.EPHEMERAL_AGENT_ID);
|
|
|
|
|
|
|
|
|
|
for (const cap of defaultAgentCapabilities) {
|
|
|
|
|
expect(result.has(cap)).toBe(true);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return empty set when no capabilities and not ephemeral', async () => {
|
|
|
|
|
const req = createMockReq([]);
|
|
|
|
|
mockGetEndpointsConfig.mockResolvedValue({});
|
|
|
|
|
|
|
|
|
|
const result = await resolveAgentCapabilities(req, req.config, 'agent_123');
|
|
|
|
|
|
|
|
|
|
expect(result.size).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('loadAgentTools (definitionsOnly=true) — action tool filtering', () => {
|
|
|
|
|
const actionToolName = `get_weather${actionDelimiter}api_example_com`;
|
|
|
|
|
const regularTool = 'calculator';
|
|
|
|
|
|
|
|
|
|
it('should exclude action tools from definitions when actions capability is disabled', async () => {
|
|
|
|
|
const capabilities = [AgentCapabilities.tools, AgentCapabilities.web_search];
|
|
|
|
|
const req = createMockReq(capabilities);
|
|
|
|
|
mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities));
|
|
|
|
|
|
|
|
|
|
await loadAgentTools({
|
|
|
|
|
req,
|
|
|
|
|
res: {},
|
|
|
|
|
agent: { id: 'agent_123', tools: [regularTool, actionToolName] },
|
|
|
|
|
definitionsOnly: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(mockLoadToolDefinitions).toHaveBeenCalledTimes(1);
|
|
|
|
|
const [callArgs] = mockLoadToolDefinitions.mock.calls[0];
|
|
|
|
|
expect(callArgs.tools).toContain(regularTool);
|
|
|
|
|
expect(callArgs.tools).not.toContain(actionToolName);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should include action tools in definitions when actions capability is enabled', async () => {
|
|
|
|
|
const capabilities = [AgentCapabilities.tools, AgentCapabilities.actions];
|
|
|
|
|
const req = createMockReq(capabilities);
|
|
|
|
|
mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities));
|
|
|
|
|
|
|
|
|
|
await loadAgentTools({
|
|
|
|
|
req,
|
|
|
|
|
res: {},
|
|
|
|
|
agent: { id: 'agent_123', tools: [regularTool, actionToolName] },
|
|
|
|
|
definitionsOnly: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(mockLoadToolDefinitions).toHaveBeenCalledTimes(1);
|
|
|
|
|
const [callArgs] = mockLoadToolDefinitions.mock.calls[0];
|
|
|
|
|
expect(callArgs.tools).toContain(regularTool);
|
|
|
|
|
expect(callArgs.tools).toContain(actionToolName);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return actionsEnabled in the result', async () => {
|
|
|
|
|
const capabilities = [AgentCapabilities.tools];
|
|
|
|
|
const req = createMockReq(capabilities);
|
|
|
|
|
mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities));
|
|
|
|
|
|
|
|
|
|
const result = await loadAgentTools({
|
|
|
|
|
req,
|
|
|
|
|
res: {},
|
|
|
|
|
agent: { id: 'agent_123', tools: [regularTool] },
|
|
|
|
|
definitionsOnly: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(result.actionsEnabled).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('loadAgentTools (definitionsOnly=false) — action tool filtering', () => {
|
|
|
|
|
const actionToolName = `get_weather${actionDelimiter}api_example_com`;
|
|
|
|
|
const regularTool = 'calculator';
|
|
|
|
|
|
|
|
|
|
it('should not load action sets when actions capability is disabled', async () => {
|
|
|
|
|
const capabilities = [AgentCapabilities.tools, AgentCapabilities.web_search];
|
|
|
|
|
const req = createMockReq(capabilities);
|
|
|
|
|
mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities));
|
|
|
|
|
|
|
|
|
|
await loadAgentTools({
|
|
|
|
|
req,
|
|
|
|
|
res: {},
|
|
|
|
|
agent: { id: 'agent_123', tools: [regularTool, actionToolName] },
|
|
|
|
|
definitionsOnly: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(mockLoadActionSets).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should load action sets when actions capability is enabled and action tools present', async () => {
|
|
|
|
|
const capabilities = [AgentCapabilities.tools, AgentCapabilities.actions];
|
|
|
|
|
const req = createMockReq(capabilities);
|
|
|
|
|
mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities));
|
|
|
|
|
|
|
|
|
|
await loadAgentTools({
|
|
|
|
|
req,
|
|
|
|
|
res: {},
|
|
|
|
|
agent: { id: 'agent_123', tools: [regularTool, actionToolName] },
|
|
|
|
|
definitionsOnly: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(mockLoadActionSets).toHaveBeenCalledWith({ agent_id: 'agent_123' });
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('loadToolsForExecution — action tool gating', () => {
|
|
|
|
|
const actionToolName = `get_weather${actionDelimiter}api_example_com`;
|
|
|
|
|
const regularTool = Tools.web_search;
|
|
|
|
|
|
|
|
|
|
it('should skip action tool loading when actionsEnabled=false', async () => {
|
|
|
|
|
const req = createMockReq([]);
|
|
|
|
|
req.config = {};
|
|
|
|
|
|
|
|
|
|
const result = await loadToolsForExecution({
|
|
|
|
|
req,
|
|
|
|
|
res: {},
|
|
|
|
|
agent: { id: 'agent_123' },
|
|
|
|
|
toolNames: [regularTool, actionToolName],
|
|
|
|
|
actionsEnabled: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(mockLoadActionSets).not.toHaveBeenCalled();
|
|
|
|
|
expect(result.loadedTools).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should load action tools when actionsEnabled=true', async () => {
|
|
|
|
|
const req = createMockReq([AgentCapabilities.actions]);
|
|
|
|
|
req.config = {};
|
|
|
|
|
|
|
|
|
|
await loadToolsForExecution({
|
|
|
|
|
req,
|
|
|
|
|
res: {},
|
|
|
|
|
agent: { id: 'agent_123' },
|
|
|
|
|
toolNames: [actionToolName],
|
|
|
|
|
actionsEnabled: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(mockLoadActionSets).toHaveBeenCalledWith({ agent_id: 'agent_123' });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should resolve actionsEnabled from capabilities when not explicitly provided', async () => {
|
|
|
|
|
const capabilities = [AgentCapabilities.tools];
|
|
|
|
|
const req = createMockReq(capabilities);
|
|
|
|
|
mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities));
|
|
|
|
|
|
|
|
|
|
await loadToolsForExecution({
|
|
|
|
|
req,
|
|
|
|
|
res: {},
|
|
|
|
|
agent: { id: 'agent_123' },
|
|
|
|
|
toolNames: [actionToolName],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(mockGetEndpointsConfig).toHaveBeenCalled();
|
|
|
|
|
expect(mockLoadActionSets).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not call loadActionSets when there are no action tools', async () => {
|
|
|
|
|
const req = createMockReq([AgentCapabilities.actions]);
|
|
|
|
|
req.config = {};
|
|
|
|
|
|
|
|
|
|
await loadToolsForExecution({
|
|
|
|
|
req,
|
|
|
|
|
res: {},
|
|
|
|
|
agent: { id: 'agent_123' },
|
|
|
|
|
toolNames: [regularTool],
|
|
|
|
|
actionsEnabled: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(mockLoadActionSets).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-10 20:25:34 -05:00
|
|
|
describe('checkCapability logic', () => {
|
|
|
|
|
const createCheckCapability = (enabledCapabilities, logger = { warn: jest.fn() }) => {
|
|
|
|
|
return (capability) => {
|
|
|
|
|
const enabled = enabledCapabilities.has(capability);
|
|
|
|
|
if (!enabled) {
|
|
|
|
|
const isToolCapability = [
|
|
|
|
|
AgentCapabilities.file_search,
|
|
|
|
|
AgentCapabilities.execute_code,
|
|
|
|
|
AgentCapabilities.web_search,
|
|
|
|
|
].includes(capability);
|
|
|
|
|
const suffix = isToolCapability ? ' despite configured tool.' : '.';
|
|
|
|
|
logger.warn(`Capability "${capability}" disabled${suffix}`);
|
|
|
|
|
}
|
|
|
|
|
return enabled;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
it('should return true when capability is enabled', () => {
|
|
|
|
|
const enabledCapabilities = new Set([AgentCapabilities.deferred_tools]);
|
|
|
|
|
const checkCapability = createCheckCapability(enabledCapabilities);
|
|
|
|
|
|
|
|
|
|
expect(checkCapability(AgentCapabilities.deferred_tools)).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return false when capability is not enabled', () => {
|
|
|
|
|
const enabledCapabilities = new Set([]);
|
|
|
|
|
const checkCapability = createCheckCapability(enabledCapabilities);
|
|
|
|
|
|
|
|
|
|
expect(checkCapability(AgentCapabilities.deferred_tools)).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should log warning with "despite configured tool" for tool capabilities', () => {
|
|
|
|
|
const logger = { warn: jest.fn() };
|
|
|
|
|
const enabledCapabilities = new Set([]);
|
|
|
|
|
const checkCapability = createCheckCapability(enabledCapabilities, logger);
|
|
|
|
|
|
|
|
|
|
checkCapability(AgentCapabilities.file_search);
|
|
|
|
|
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('despite configured tool'));
|
|
|
|
|
|
|
|
|
|
logger.warn.mockClear();
|
|
|
|
|
checkCapability(AgentCapabilities.execute_code);
|
|
|
|
|
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('despite configured tool'));
|
|
|
|
|
|
|
|
|
|
logger.warn.mockClear();
|
|
|
|
|
checkCapability(AgentCapabilities.web_search);
|
|
|
|
|
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('despite configured tool'));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should log warning without "despite configured tool" for non-tool capabilities', () => {
|
|
|
|
|
const logger = { warn: jest.fn() };
|
|
|
|
|
const enabledCapabilities = new Set([]);
|
|
|
|
|
const checkCapability = createCheckCapability(enabledCapabilities, logger);
|
|
|
|
|
|
|
|
|
|
checkCapability(AgentCapabilities.deferred_tools);
|
|
|
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
|
|
|
expect.stringContaining('Capability "deferred_tools" disabled.'),
|
|
|
|
|
);
|
|
|
|
|
expect(logger.warn).not.toHaveBeenCalledWith(
|
|
|
|
|
expect.stringContaining('despite configured tool'),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
logger.warn.mockClear();
|
|
|
|
|
checkCapability(AgentCapabilities.tools);
|
|
|
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
|
|
|
expect.stringContaining('Capability "tools" disabled.'),
|
|
|
|
|
);
|
|
|
|
|
expect(logger.warn).not.toHaveBeenCalledWith(
|
|
|
|
|
expect.stringContaining('despite configured tool'),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
logger.warn.mockClear();
|
|
|
|
|
checkCapability(AgentCapabilities.actions);
|
|
|
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
|
|
|
expect.stringContaining('Capability "actions" disabled.'),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not log warning when capability is enabled', () => {
|
|
|
|
|
const logger = { warn: jest.fn() };
|
|
|
|
|
const enabledCapabilities = new Set([
|
|
|
|
|
AgentCapabilities.deferred_tools,
|
|
|
|
|
AgentCapabilities.file_search,
|
|
|
|
|
]);
|
|
|
|
|
const checkCapability = createCheckCapability(enabledCapabilities, logger);
|
|
|
|
|
|
|
|
|
|
checkCapability(AgentCapabilities.deferred_tools);
|
|
|
|
|
checkCapability(AgentCapabilities.file_search);
|
|
|
|
|
|
|
|
|
|
expect(logger.warn).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('defaultAgentCapabilities', () => {
|
|
|
|
|
it('should include deferred_tools capability by default', () => {
|
|
|
|
|
expect(defaultAgentCapabilities).toContain(AgentCapabilities.deferred_tools);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should include all expected default capabilities', () => {
|
|
|
|
|
expect(defaultAgentCapabilities).toContain(AgentCapabilities.execute_code);
|
|
|
|
|
expect(defaultAgentCapabilities).toContain(AgentCapabilities.file_search);
|
|
|
|
|
expect(defaultAgentCapabilities).toContain(AgentCapabilities.web_search);
|
|
|
|
|
expect(defaultAgentCapabilities).toContain(AgentCapabilities.artifacts);
|
|
|
|
|
expect(defaultAgentCapabilities).toContain(AgentCapabilities.actions);
|
|
|
|
|
expect(defaultAgentCapabilities).toContain(AgentCapabilities.context);
|
|
|
|
|
expect(defaultAgentCapabilities).toContain(AgentCapabilities.tools);
|
|
|
|
|
expect(defaultAgentCapabilities).toContain(AgentCapabilities.chain);
|
|
|
|
|
expect(defaultAgentCapabilities).toContain(AgentCapabilities.ocr);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
🗝️ feat: Credential Variables for DB-Sourced MCP Servers (#12044)
* feat: Allow Credential Variables in Headers for DB-sourced MCP Servers
- Removed the hasCustomUserVars check from ToolService.js, directly retrieving userMCPAuthMap.
- Updated MCPConnectionFactory and related classes to include a dbSourced flag for better handling of database-sourced configurations.
- Added integration tests to ensure proper behavior of dbSourced servers, verifying that sensitive placeholders are not resolved while allowing customUserVars.
- Adjusted various MCP-related files to accommodate the new dbSourced logic, ensuring consistent handling across the codebase.
* chore: MCPConnectionFactory Tests with Additional Flow Metadata for typing
- Updated MCPConnectionFactory tests to include new fields in flowMetadata: serverUrl and state.
- Enhanced mockFlowData in multiple test cases to reflect the updated structure, ensuring comprehensive coverage of the OAuth flow scenarios.
- Added authorization_endpoint to metadata in the test setup for improved validation of the OAuth process.
* refactor: Simplify MCPManager Configuration Handling
- Removed unnecessary type assertions and streamlined the retrieval of server configuration in MCPManager.
- Enhanced the handling of OAuth and database-sourced flags for improved clarity and efficiency.
- Updated tests to reflect changes in user object structure and ensure proper processing of MCP environment variables.
* refactor: Optimize User MCP Auth Map Retrieval in ToolService
- Introduced conditional loading of userMCPAuthMap based on the presence of MCP-delimited tools, improving efficiency by avoiding unnecessary calls.
- Updated the loadToolDefinitionsWrapper and loadAgentTools functions to reflect this change, enhancing overall performance and clarity.
* test: Add userMCPAuthMap gating tests in ToolService
- Introduced new tests to validate the logic for determining if MCP tools are present in the agent's tool list.
- Implemented various scenarios to ensure accurate detection of MCP tools, including edge cases for empty, undefined, and null tool lists.
- Enhanced clarity and coverage of the ToolService capability checking logic.
* refactor: Enhance MCP Environment Variable Processing
- Simplified the handling of the dbSourced parameter in the processMCPEnv function.
- Introduced a failsafe mechanism to derive dbSourced from options if not explicitly provided, improving robustness and clarity in MCP environment variable processing.
* refactor: Update Regex Patterns for Credential Placeholders in ServerConfigsDB
- Modified regex patterns to include additional credential/env placeholders that should not be allowed in user-provided configurations.
- Clarified comments to emphasize the security risks associated with credential exfiltration when MCP servers are shared between users.
* chore: field order
* refactor: Clean Up dbSourced Parameter Handling in processMCPEnv
- Reintroduced the failsafe mechanism for deriving the dbSourced parameter from options, ensuring clarity and robustness in MCP environment variable processing.
- Enhanced code readability by maintaining consistent comment structure.
* refactor: Update MCPOptions Type to Include Optional dbId
- Modified the processMCPEnv function to extend the MCPOptions type, allowing for an optional dbId property.
- Simplified the logic for deriving the dbSourced parameter by directly checking the dbId property, enhancing code clarity and maintainability.
2026-03-03 18:02:37 -05:00
|
|
|
describe('userMCPAuthMap gating', () => {
|
|
|
|
|
const shouldFetchMCPAuth = (tools) =>
|
|
|
|
|
tools?.some((t) => t.includes(Constants.mcp_delimiter)) ?? false;
|
|
|
|
|
|
|
|
|
|
it('should return true when agent has MCP tools', () => {
|
|
|
|
|
const tools = ['web_search', `search${Constants.mcp_delimiter}my-mcp-server`, 'calculator'];
|
|
|
|
|
expect(shouldFetchMCPAuth(tools)).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return false when agent has no MCP tools', () => {
|
|
|
|
|
const tools = ['web_search', 'calculator', 'code_interpreter'];
|
|
|
|
|
expect(shouldFetchMCPAuth(tools)).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return false when tools is empty', () => {
|
|
|
|
|
expect(shouldFetchMCPAuth([])).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return false when tools is undefined', () => {
|
|
|
|
|
expect(shouldFetchMCPAuth(undefined)).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return false when tools is null', () => {
|
|
|
|
|
expect(shouldFetchMCPAuth(null)).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should detect MCP tools with different server names', () => {
|
|
|
|
|
const tools = [
|
|
|
|
|
`listFiles${Constants.mcp_delimiter}file-server`,
|
|
|
|
|
`query${Constants.mcp_delimiter}db-server`,
|
|
|
|
|
];
|
|
|
|
|
expect(shouldFetchMCPAuth(tools)).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return true even when only one tool is MCP', () => {
|
|
|
|
|
const tools = [
|
|
|
|
|
'web_search',
|
|
|
|
|
'calculator',
|
|
|
|
|
'code_interpreter',
|
|
|
|
|
`echo${Constants.mcp_delimiter}test-server`,
|
|
|
|
|
];
|
|
|
|
|
expect(shouldFetchMCPAuth(tools)).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-10 20:25:34 -05:00
|
|
|
describe('deferredToolsEnabled integration', () => {
|
|
|
|
|
it('should correctly determine deferredToolsEnabled from capabilities set', () => {
|
|
|
|
|
const createCheckCapability = (enabledCapabilities) => {
|
|
|
|
|
return (capability) => enabledCapabilities.has(capability);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const withDeferred = new Set([AgentCapabilities.deferred_tools, AgentCapabilities.tools]);
|
|
|
|
|
const checkWithDeferred = createCheckCapability(withDeferred);
|
|
|
|
|
expect(checkWithDeferred(AgentCapabilities.deferred_tools)).toBe(true);
|
|
|
|
|
|
|
|
|
|
const withoutDeferred = new Set([AgentCapabilities.tools, AgentCapabilities.actions]);
|
|
|
|
|
const checkWithoutDeferred = createCheckCapability(withoutDeferred);
|
|
|
|
|
expect(checkWithoutDeferred(AgentCapabilities.deferred_tools)).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should use defaultAgentCapabilities when no capabilities configured', () => {
|
🛂 fix: Enforce Actions Capability Gate Across All Event-Driven Tool Loading Paths (#12252)
* fix: gate action tools by actions capability in all code paths
Extract resolveAgentCapabilities helper to eliminate 3x-duplicated
capability resolution. Apply early action-tool filtering in both
loadToolDefinitionsWrapper and loadAgentTools non-definitions path.
Gate loadActionToolsForExecution in loadToolsForExecution behind an
actionsEnabled parameter with a cache-based fallback. Replace the
late capability guard in loadAgentTools with a hasActionTools check
to avoid unnecessary loadActionSets DB calls and duplicate warnings.
* fix: thread actionsEnabled through InitializedAgent type
Add actionsEnabled to the loadTools callback return type,
InitializedAgent, and the initializeAgent destructuring/return
so callers can forward the resolved value to loadToolsForExecution
without redundant getEndpointsConfig cache lookups.
* fix: pass actionsEnabled from callers to loadToolsForExecution
Thread actionsEnabled through the agentToolContexts map in
initialize.js (primary and handoff agents) and through
primaryConfig in the openai.js and responses.js controllers,
avoiding per-tool-call capability re-resolution on the hot path.
* test: add regression tests for action capability gating
Test the real exported functions (resolveAgentCapabilities,
loadAgentTools, loadToolsForExecution) with mocked dependencies
instead of shadow re-implementations. Covers definition filtering,
execution gating, actionsEnabled param forwarding, and fallback
capability resolution.
* test: use Constants.EPHEMERAL_AGENT_ID in ephemeral fallback test
Replaces a string guess with the canonical constant to avoid
fragility if the ephemeral detection heuristic changes.
* fix: populate agentToolContexts for addedConvo parallel agents
After processAddedConvo returns, backfill agentToolContexts for
any agents in agentConfigs not already present, so ON_TOOL_EXECUTE
for added-convo agents receives actionsEnabled instead of falling
back to a per-call cache lookup.
2026-03-15 23:01:36 -04:00
|
|
|
const endpointsConfig = {};
|
2026-01-10 20:25:34 -05:00
|
|
|
const enabledCapabilities = new Set(
|
|
|
|
|
endpointsConfig?.capabilities ?? defaultAgentCapabilities,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(enabledCapabilities.has(AgentCapabilities.deferred_tools)).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|