@google/gemini-cli-core 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -3
- package/dist/google-gemini-cli-core-0.1.13.tgz +0 -0
- package/dist/src/code_assist/codeAssist.js +2 -2
- package/dist/src/code_assist/codeAssist.js.map +1 -1
- package/dist/src/code_assist/oauth2.js +46 -5
- package/dist/src/code_assist/oauth2.js.map +1 -1
- package/dist/src/code_assist/oauth2.test.js +100 -3
- package/dist/src/code_assist/oauth2.test.js.map +1 -1
- package/dist/src/code_assist/server.d.ts +4 -6
- package/dist/src/code_assist/server.js +4 -69
- package/dist/src/code_assist/server.js.map +1 -1
- package/dist/src/code_assist/server.test.js +10 -2
- package/dist/src/code_assist/server.test.js.map +1 -1
- package/dist/src/code_assist/setup.d.ts +6 -1
- package/dist/src/code_assist/setup.js +4 -1
- package/dist/src/code_assist/setup.js.map +1 -1
- package/dist/src/code_assist/setup.test.js +4 -1
- package/dist/src/code_assist/setup.test.js.map +1 -1
- package/dist/src/code_assist/types.d.ts +2 -2
- package/dist/src/config/config.d.ts +52 -11
- package/dist/src/config/config.js +79 -33
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +29 -26
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/flashFallback.test.js +1 -1
- package/dist/src/config/flashFallback.test.js.map +1 -1
- package/dist/src/core/client.d.ts +8 -3
- package/dist/src/core/client.js +62 -3
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +145 -37
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/contentGenerator.d.ts +3 -2
- package/dist/src/core/contentGenerator.js +5 -4
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/contentGenerator.test.js +12 -5
- package/dist/src/core/contentGenerator.test.js.map +1 -1
- package/dist/src/core/coreToolScheduler.js +14 -1
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +84 -2
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +4 -3
- package/dist/src/core/geminiChat.js +8 -11
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiRequest.js +2 -37
- package/dist/src/core/geminiRequest.js.map +1 -1
- package/dist/src/core/logger.js +6 -0
- package/dist/src/core/logger.js.map +1 -1
- package/dist/src/core/logger.test.js +1 -1
- package/dist/src/core/logger.test.js.map +1 -1
- package/dist/src/core/modelCheck.d.ts +1 -1
- package/dist/src/core/modelCheck.js +10 -3
- package/dist/src/core/modelCheck.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js +8 -5
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
- package/dist/src/core/prompts.js +42 -18
- package/dist/src/core/prompts.js.map +1 -1
- package/dist/src/core/prompts.test.js +121 -4
- package/dist/src/core/prompts.test.js.map +1 -1
- package/dist/src/core/turn.d.ts +12 -3
- package/dist/src/core/turn.js +10 -0
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/core/turn.test.js +129 -0
- package/dist/src/core/turn.test.js.map +1 -1
- package/dist/src/ide/ide-client.d.ts +28 -0
- package/dist/src/ide/ide-client.js +79 -0
- package/dist/src/ide/ide-client.js.map +1 -0
- package/dist/src/ide/ideContext.d.ts +174 -0
- package/dist/src/ide/ideContext.js +101 -0
- package/dist/src/ide/ideContext.js.map +1 -0
- package/dist/src/ide/ideContext.test.js +111 -0
- package/dist/src/ide/ideContext.test.js.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.js +11 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/google-auth-provider.d.ts +23 -0
- package/dist/src/mcp/google-auth-provider.js +63 -0
- package/dist/src/mcp/google-auth-provider.js.map +1 -0
- package/dist/src/mcp/google-auth-provider.test.d.ts +6 -0
- package/dist/src/mcp/google-auth-provider.test.js +54 -0
- package/dist/src/mcp/google-auth-provider.test.js.map +1 -0
- package/dist/src/mcp/oauth-provider.d.ts +142 -0
- package/dist/src/mcp/oauth-provider.js +446 -0
- package/dist/src/mcp/oauth-provider.js.map +1 -0
- package/dist/src/mcp/oauth-provider.test.d.ts +6 -0
- package/dist/src/mcp/oauth-provider.test.js +520 -0
- package/dist/src/mcp/oauth-provider.test.js.map +1 -0
- package/dist/src/mcp/oauth-token-storage.d.ts +81 -0
- package/dist/src/mcp/oauth-token-storage.js +149 -0
- package/dist/src/mcp/oauth-token-storage.js.map +1 -0
- package/dist/src/mcp/oauth-token-storage.test.d.ts +6 -0
- package/dist/src/mcp/oauth-token-storage.test.js +205 -0
- package/dist/src/mcp/oauth-token-storage.test.js.map +1 -0
- package/dist/src/mcp/oauth-utils.d.ts +109 -0
- package/dist/src/mcp/oauth-utils.js +183 -0
- package/dist/src/mcp/oauth-utils.js.map +1 -0
- package/dist/src/mcp/oauth-utils.test.d.ts +6 -0
- package/dist/src/mcp/oauth-utils.test.js +144 -0
- package/dist/src/mcp/oauth-utils.test.js.map +1 -0
- package/dist/src/services/gitService.js +1 -5
- package/dist/src/services/gitService.js.map +1 -1
- package/dist/src/services/gitService.test.js +1 -6
- package/dist/src/services/gitService.test.js.map +1 -1
- package/dist/src/services/loopDetectionService.d.ts +94 -0
- package/dist/src/services/loopDetectionService.js +318 -0
- package/dist/src/services/loopDetectionService.js.map +1 -0
- package/dist/src/services/loopDetectionService.test.d.ts +6 -0
- package/dist/src/services/loopDetectionService.test.js +266 -0
- package/dist/src/services/loopDetectionService.test.js.map +1 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +5 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +69 -4
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +4 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +9 -0
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
- package/dist/src/telemetry/constants.d.ts +1 -0
- package/dist/src/telemetry/constants.js +1 -0
- package/dist/src/telemetry/constants.js.map +1 -1
- package/dist/src/telemetry/file-exporters.d.ts +28 -0
- package/dist/src/telemetry/file-exporters.js +62 -0
- package/dist/src/telemetry/file-exporters.js.map +1 -0
- package/dist/src/telemetry/integration.test.circular.d.ts +6 -0
- package/dist/src/telemetry/integration.test.circular.js +53 -0
- package/dist/src/telemetry/integration.test.circular.js.map +1 -0
- package/dist/src/telemetry/loggers.d.ts +3 -1
- package/dist/src/telemetry/loggers.js +34 -2
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.circular.d.ts +6 -0
- package/dist/src/telemetry/loggers.test.circular.js +100 -0
- package/dist/src/telemetry/loggers.test.circular.js.map +1 -0
- package/dist/src/telemetry/sdk.js +17 -6
- package/dist/src/telemetry/sdk.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +19 -1
- package/dist/src/telemetry/types.js +28 -0
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.d.ts +1 -0
- package/dist/src/telemetry/uiTelemetry.js +7 -0
- package/dist/src/telemetry/uiTelemetry.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.test.js +92 -0
- package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
- package/dist/src/tools/edit.d.ts +7 -12
- package/dist/src/tools/edit.js +34 -32
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/edit.test.js +12 -0
- package/dist/src/tools/edit.test.js.map +1 -1
- package/dist/src/tools/glob.d.ts +1 -14
- package/dist/src/tools/glob.js +13 -36
- package/dist/src/tools/glob.js.map +1 -1
- package/dist/src/tools/glob.test.js +4 -3
- package/dist/src/tools/glob.test.js.map +1 -1
- package/dist/src/tools/grep.d.ts +3 -6
- package/dist/src/tools/grep.js +12 -18
- package/dist/src/tools/grep.js.map +1 -1
- package/dist/src/tools/grep.test.js +5 -2
- package/dist/src/tools/grep.test.js.map +1 -1
- package/dist/src/tools/ls.d.ts +6 -14
- package/dist/src/tools/ls.js +47 -40
- package/dist/src/tools/ls.js.map +1 -1
- package/dist/src/tools/mcp-client.d.ts +59 -1
- package/dist/src/tools/mcp-client.js +557 -146
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-client.test.js +166 -623
- package/dist/src/tools/mcp-client.test.js.map +1 -1
- package/dist/src/tools/mcp-tool.d.ts +11 -5
- package/dist/src/tools/mcp-tool.js +34 -10
- package/dist/src/tools/mcp-tool.js.map +1 -1
- package/dist/src/tools/mcp-tool.test.js +74 -24
- package/dist/src/tools/mcp-tool.test.js.map +1 -1
- package/dist/src/tools/memoryTool.js +2 -2
- package/dist/src/tools/memoryTool.js.map +1 -1
- package/dist/src/tools/read-file.d.ts +3 -3
- package/dist/src/tools/read-file.js +10 -10
- package/dist/src/tools/read-file.js.map +1 -1
- package/dist/src/tools/read-file.test.js +100 -70
- package/dist/src/tools/read-file.test.js.map +1 -1
- package/dist/src/tools/read-many-files.d.ts +6 -10
- package/dist/src/tools/read-many-files.js +74 -43
- package/dist/src/tools/read-many-files.js.map +1 -1
- package/dist/src/tools/read-many-files.test.js +7 -3
- package/dist/src/tools/read-many-files.test.js.map +1 -1
- package/dist/src/tools/shell.d.ts +2 -23
- package/dist/src/tools/shell.js +58 -138
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +85 -311
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/tool-registry.d.ts +13 -2
- package/dist/src/tools/tool-registry.js +57 -10
- package/dist/src/tools/tool-registry.js.map +1 -1
- package/dist/src/tools/tool-registry.test.js +112 -41
- package/dist/src/tools/tool-registry.test.js.map +1 -1
- package/dist/src/tools/tools.d.ts +37 -2
- package/dist/src/tools/tools.js +25 -2
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/web-fetch.js +7 -2
- package/dist/src/tools/web-fetch.js.map +1 -1
- package/dist/src/tools/web-fetch.test.js +1 -0
- package/dist/src/tools/web-fetch.test.js.map +1 -1
- package/dist/src/tools/web-search.js +2 -2
- package/dist/src/tools/web-search.js.map +1 -1
- package/dist/src/tools/write-file.d.ts +0 -8
- package/dist/src/tools/write-file.js +14 -23
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/utils/bfsFileSearch.d.ts +2 -0
- package/dist/src/utils/bfsFileSearch.js +4 -1
- package/dist/src/utils/bfsFileSearch.js.map +1 -1
- package/dist/src/utils/bfsFileSearch.test.js +108 -105
- package/dist/src/utils/bfsFileSearch.test.js.map +1 -1
- package/dist/src/utils/browser.d.ts +13 -0
- package/dist/src/utils/browser.js +49 -0
- package/dist/src/utils/browser.js.map +1 -0
- package/dist/src/utils/editCorrector.js +4 -4
- package/dist/src/utils/editCorrector.js.map +1 -1
- package/dist/src/utils/editCorrector.test.js +1 -1
- package/dist/src/utils/editor.js +16 -10
- package/dist/src/utils/editor.js.map +1 -1
- package/dist/src/utils/editor.test.js +128 -28
- package/dist/src/utils/editor.test.js.map +1 -1
- package/dist/src/utils/errorReporting.d.ts +1 -1
- package/dist/src/utils/errorReporting.js +2 -2
- package/dist/src/utils/errorReporting.js.map +1 -1
- package/dist/src/utils/errorReporting.test.js +44 -38
- package/dist/src/utils/errorReporting.test.js.map +1 -1
- package/dist/src/utils/errors.js +4 -4
- package/dist/src/utils/errors.js.map +1 -1
- package/dist/src/utils/fileUtils.d.ts +4 -4
- package/dist/src/utils/fileUtils.js +33 -17
- package/dist/src/utils/fileUtils.js.map +1 -1
- package/dist/src/utils/fileUtils.test.js +37 -37
- package/dist/src/utils/fileUtils.test.js.map +1 -1
- package/dist/src/utils/getFolderStructure.d.ts +3 -2
- package/dist/src/utils/getFolderStructure.js +27 -28
- package/dist/src/utils/getFolderStructure.js.map +1 -1
- package/dist/src/utils/getFolderStructure.test.js +169 -187
- package/dist/src/utils/getFolderStructure.test.js.map +1 -1
- package/dist/src/utils/gitIgnoreParser.js +4 -7
- package/dist/src/utils/gitIgnoreParser.js.map +1 -1
- package/dist/src/utils/gitIgnoreParser.test.js +70 -61
- package/dist/src/utils/gitIgnoreParser.test.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.d.ts +2 -1
- package/dist/src/utils/memoryDiscovery.js +11 -5
- package/dist/src/utils/memoryDiscovery.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.test.js +160 -371
- package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
- package/dist/src/utils/partUtils.d.ts +14 -0
- package/dist/src/utils/partUtils.js +65 -0
- package/dist/src/utils/partUtils.js.map +1 -0
- package/dist/src/utils/partUtils.test.d.ts +6 -0
- package/dist/src/utils/partUtils.test.js +130 -0
- package/dist/src/utils/partUtils.test.js.map +1 -0
- package/dist/src/utils/paths.d.ts +11 -0
- package/dist/src/utils/paths.js +17 -1
- package/dist/src/utils/paths.js.map +1 -1
- package/dist/src/utils/quotaErrorDetection.js +2 -11
- package/dist/src/utils/quotaErrorDetection.js.map +1 -1
- package/dist/src/utils/retry.d.ts +6 -0
- package/dist/src/utils/retry.js +2 -2
- package/dist/src/utils/retry.js.map +1 -1
- package/dist/src/utils/safeJsonStringify.d.ts +13 -0
- package/dist/src/utils/safeJsonStringify.js +25 -0
- package/dist/src/utils/safeJsonStringify.js.map +1 -0
- package/dist/src/utils/safeJsonStringify.test.d.ts +6 -0
- package/dist/src/utils/safeJsonStringify.test.js +61 -0
- package/dist/src/utils/safeJsonStringify.test.js.map +1 -0
- package/dist/src/utils/schemaValidator.d.ts +1 -1
- package/dist/src/utils/schemaValidator.js +6 -3
- package/dist/src/utils/schemaValidator.js.map +1 -1
- package/dist/src/utils/shell-utils.d.ts +44 -0
- package/dist/src/utils/shell-utils.js +243 -0
- package/dist/src/utils/shell-utils.js.map +1 -0
- package/dist/src/utils/shell-utils.test.d.ts +6 -0
- package/dist/src/utils/shell-utils.test.js +450 -0
- package/dist/src/utils/shell-utils.test.js.map +1 -0
- package/dist/src/utils/summarizer.d.ts +1 -1
- package/dist/src/utils/summarizer.js +11 -39
- package/dist/src/utils/summarizer.js.map +1 -1
- package/dist/src/utils/summarizer.test.js +1 -1
- package/dist/src/utils/systemEncoding.d.ts +40 -0
- package/dist/src/utils/systemEncoding.js +149 -0
- package/dist/src/utils/systemEncoding.js.map +1 -0
- package/dist/src/utils/systemEncoding.test.d.ts +6 -0
- package/dist/src/utils/systemEncoding.test.js +368 -0
- package/dist/src/utils/systemEncoding.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -3
- package/dist/google-gemini-cli-core-0.1.11.tgz +0 -0
- package/dist/src/core/geminiRequest.test.js +0 -72
- package/dist/src/core/geminiRequest.test.js.map +0 -1
- /package/dist/src/{core/geminiRequest.test.d.ts → ide/ideContext.test.d.ts} +0 -0
|
@@ -3,651 +3,194 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
|
|
8
|
-
import { discoverMcpTools } from './mcp-client.js';
|
|
9
|
-
import { sanitizeParameters } from './tool-registry.js';
|
|
10
|
-
import { Type } from '@google/genai';
|
|
11
|
-
import { DiscoveredMCPTool } from './mcp-tool.js';
|
|
12
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
13
|
-
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
14
|
-
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
6
|
+
import { afterEach, describe, expect, it, vi, beforeEach } from 'vitest';
|
|
15
7
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}));
|
|
29
|
-
return { Client: MockedClient };
|
|
30
|
-
});
|
|
31
|
-
// Define a global mock for stderr.on that can be cleared and checked
|
|
32
|
-
const mockGlobalStdioStderrOn = vi.fn();
|
|
33
|
-
vi.mock('@modelcontextprotocol/sdk/client/stdio.js', () => {
|
|
34
|
-
// This is the constructor for StdioClientTransport
|
|
35
|
-
const MockedStdioTransport = vi.fn().mockImplementation(function (options) {
|
|
36
|
-
// Always return a new object with a fresh reference to the global mock for .on
|
|
37
|
-
this.options = options;
|
|
38
|
-
this.stderr = { on: mockGlobalStdioStderrOn };
|
|
39
|
-
this.close = vi.fn().mockResolvedValue(undefined); // Add mock close method
|
|
40
|
-
return this;
|
|
41
|
-
});
|
|
42
|
-
return { StdioClientTransport: MockedStdioTransport };
|
|
43
|
-
});
|
|
44
|
-
vi.mock('@modelcontextprotocol/sdk/client/sse.js', () => {
|
|
45
|
-
const MockedSSETransport = vi.fn().mockImplementation(function () {
|
|
46
|
-
this.close = vi.fn().mockResolvedValue(undefined); // Add mock close method
|
|
47
|
-
return this;
|
|
48
|
-
});
|
|
49
|
-
return { SSEClientTransport: MockedSSETransport };
|
|
50
|
-
});
|
|
51
|
-
vi.mock('@modelcontextprotocol/sdk/client/streamableHttp.js', () => {
|
|
52
|
-
const MockedStreamableHTTPTransport = vi.fn().mockImplementation(function () {
|
|
53
|
-
this.close = vi.fn().mockResolvedValue(undefined); // Add mock close method
|
|
54
|
-
return this;
|
|
55
|
-
});
|
|
56
|
-
return { StreamableHTTPClientTransport: MockedStreamableHTTPTransport };
|
|
57
|
-
});
|
|
58
|
-
const mockToolRegistryInstance = {
|
|
59
|
-
registerTool: vi.fn(),
|
|
60
|
-
getToolsByServer: vi.fn().mockReturnValue([]), // Default to empty array
|
|
61
|
-
// Add other methods if they are called by the code under test, with default mocks
|
|
62
|
-
getTool: vi.fn(),
|
|
63
|
-
getAllTools: vi.fn().mockReturnValue([]),
|
|
64
|
-
getFunctionDeclarations: vi.fn().mockReturnValue([]),
|
|
65
|
-
discoverTools: vi.fn().mockResolvedValue(undefined),
|
|
66
|
-
};
|
|
67
|
-
vi.mock('./tool-registry.js', async (importOriginal) => {
|
|
68
|
-
const actual = await importOriginal();
|
|
69
|
-
return {
|
|
70
|
-
...actual,
|
|
71
|
-
ToolRegistry: vi.fn(() => mockToolRegistryInstance),
|
|
72
|
-
sanitizeParameters: actual.sanitizeParameters,
|
|
73
|
-
};
|
|
74
|
-
});
|
|
75
|
-
describe('discoverMcpTools', () => {
|
|
76
|
-
let mockConfig;
|
|
77
|
-
// Use the instance from the module mock
|
|
78
|
-
let mockToolRegistry;
|
|
79
|
-
beforeEach(() => {
|
|
80
|
-
// Assign the shared mock instance to the test-scoped variable
|
|
81
|
-
mockToolRegistry = mockToolRegistryInstance;
|
|
82
|
-
// Reset individual spies on the shared instance before each test
|
|
83
|
-
mockToolRegistry.registerTool.mockClear();
|
|
84
|
-
mockToolRegistry.getToolsByServer.mockClear().mockReturnValue([]); // Reset to default
|
|
85
|
-
mockToolRegistry.getTool.mockClear().mockReturnValue(undefined); // Default to no existing tool
|
|
86
|
-
mockToolRegistry.getAllTools.mockClear().mockReturnValue([]);
|
|
87
|
-
mockToolRegistry.getFunctionDeclarations.mockClear().mockReturnValue([]);
|
|
88
|
-
mockToolRegistry.discoverTools.mockClear().mockResolvedValue(undefined);
|
|
89
|
-
mockConfig = {
|
|
90
|
-
getMcpServers: vi.fn().mockReturnValue({}),
|
|
91
|
-
getMcpServerCommand: vi.fn().mockReturnValue(undefined),
|
|
92
|
-
// getToolRegistry should now return the same shared mock instance
|
|
93
|
-
getToolRegistry: vi.fn(() => mockToolRegistry),
|
|
94
|
-
};
|
|
95
|
-
vi.mocked(parse).mockClear();
|
|
96
|
-
vi.mocked(Client).mockClear();
|
|
97
|
-
vi.mocked(Client.prototype.connect)
|
|
98
|
-
.mockClear()
|
|
99
|
-
.mockResolvedValue(undefined);
|
|
100
|
-
vi.mocked(Client.prototype.listTools)
|
|
101
|
-
.mockClear()
|
|
102
|
-
.mockResolvedValue({ tools: [] });
|
|
103
|
-
vi.mocked(StdioClientTransport).mockClear();
|
|
104
|
-
// Ensure the StdioClientTransport mock constructor returns an object with a close method
|
|
105
|
-
vi.mocked(StdioClientTransport).mockImplementation(function (options) {
|
|
106
|
-
this.options = options;
|
|
107
|
-
this.stderr = { on: mockGlobalStdioStderrOn };
|
|
108
|
-
this.close = vi.fn().mockResolvedValue(undefined);
|
|
109
|
-
return this;
|
|
110
|
-
});
|
|
111
|
-
mockGlobalStdioStderrOn.mockClear(); // Clear the global mock in beforeEach
|
|
112
|
-
vi.mocked(SSEClientTransport).mockClear();
|
|
113
|
-
// Ensure the SSEClientTransport mock constructor returns an object with a close method
|
|
114
|
-
vi.mocked(SSEClientTransport).mockImplementation(function () {
|
|
115
|
-
this.close = vi.fn().mockResolvedValue(undefined);
|
|
116
|
-
return this;
|
|
117
|
-
});
|
|
118
|
-
vi.mocked(StreamableHTTPClientTransport).mockClear();
|
|
119
|
-
// Ensure the StreamableHTTPClientTransport mock constructor returns an object with a close method
|
|
120
|
-
vi.mocked(StreamableHTTPClientTransport).mockImplementation(function () {
|
|
121
|
-
this.close = vi.fn().mockResolvedValue(undefined);
|
|
122
|
-
return this;
|
|
123
|
-
});
|
|
124
|
-
});
|
|
8
|
+
import { populateMcpServerCommand, createTransport, isEnabled, discoverTools, } from './mcp-client.js';
|
|
9
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
10
|
+
import * as SdkClientStdioLib from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
11
|
+
import * as GenAiLib from '@google/genai';
|
|
12
|
+
import { GoogleCredentialProvider } from '../mcp/google-auth-provider.js';
|
|
13
|
+
import { AuthProviderType } from '../config/config.js';
|
|
14
|
+
vi.mock('@modelcontextprotocol/sdk/client/stdio.js');
|
|
15
|
+
vi.mock('@modelcontextprotocol/sdk/client/index.js');
|
|
16
|
+
vi.mock('@google/genai');
|
|
17
|
+
vi.mock('../mcp/oauth-provider.js');
|
|
18
|
+
vi.mock('../mcp/oauth-token-storage.js');
|
|
19
|
+
describe('mcp-client', () => {
|
|
125
20
|
afterEach(() => {
|
|
126
21
|
vi.restoreAllMocks();
|
|
127
22
|
});
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
vi.mocked(parse).mockReturnValue(parsedCommand);
|
|
140
|
-
const mockTool = {
|
|
141
|
-
name: 'tool1',
|
|
142
|
-
description: 'desc1',
|
|
143
|
-
inputSchema: { type: 'object', properties: {} },
|
|
144
|
-
};
|
|
145
|
-
vi.mocked(Client.prototype.listTools).mockResolvedValue({
|
|
146
|
-
tools: [mockTool],
|
|
147
|
-
});
|
|
148
|
-
// PRE-MOCK getToolsByServer for the expected server name
|
|
149
|
-
// In this case, listTools fails, so no tools are registered.
|
|
150
|
-
// The default mock `mockReturnValue([])` from beforeEach should apply.
|
|
151
|
-
await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
|
|
152
|
-
expect(parse).toHaveBeenCalledWith(commandString, process.env);
|
|
153
|
-
expect(StdioClientTransport).toHaveBeenCalledWith({
|
|
154
|
-
command: parsedCommand[0],
|
|
155
|
-
args: parsedCommand.slice(1),
|
|
156
|
-
env: expect.any(Object),
|
|
157
|
-
cwd: undefined,
|
|
158
|
-
stderr: 'pipe',
|
|
159
|
-
});
|
|
160
|
-
expect(Client.prototype.connect).toHaveBeenCalledTimes(1);
|
|
161
|
-
expect(Client.prototype.listTools).toHaveBeenCalledTimes(1);
|
|
162
|
-
expect(mockToolRegistry.registerTool).toHaveBeenCalledTimes(1);
|
|
163
|
-
expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.any(DiscoveredMCPTool));
|
|
164
|
-
const registeredTool = mockToolRegistry.registerTool.mock
|
|
165
|
-
.calls[0][0];
|
|
166
|
-
expect(registeredTool.name).toBe('tool1');
|
|
167
|
-
expect(registeredTool.serverToolName).toBe('tool1');
|
|
168
|
-
});
|
|
169
|
-
it('should discover tools via mcpServers config (stdio)', async () => {
|
|
170
|
-
const serverConfig = {
|
|
171
|
-
command: './mcp-stdio',
|
|
172
|
-
args: ['arg1'],
|
|
173
|
-
};
|
|
174
|
-
mockConfig.getMcpServers.mockReturnValue({ 'stdio-server': serverConfig });
|
|
175
|
-
const mockTool = {
|
|
176
|
-
name: 'tool-stdio',
|
|
177
|
-
description: 'desc-stdio',
|
|
178
|
-
inputSchema: { type: 'object', properties: {} },
|
|
179
|
-
};
|
|
180
|
-
vi.mocked(Client.prototype.listTools).mockResolvedValue({
|
|
181
|
-
tools: [mockTool],
|
|
182
|
-
});
|
|
183
|
-
// PRE-MOCK getToolsByServer for the expected server name
|
|
184
|
-
mockToolRegistry.getToolsByServer.mockReturnValueOnce([
|
|
185
|
-
expect.any(DiscoveredMCPTool),
|
|
186
|
-
]);
|
|
187
|
-
await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
|
|
188
|
-
expect(StdioClientTransport).toHaveBeenCalledWith({
|
|
189
|
-
command: serverConfig.command,
|
|
190
|
-
args: serverConfig.args,
|
|
191
|
-
env: expect.any(Object),
|
|
192
|
-
cwd: undefined,
|
|
193
|
-
stderr: 'pipe',
|
|
194
|
-
});
|
|
195
|
-
expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.any(DiscoveredMCPTool));
|
|
196
|
-
const registeredTool = mockToolRegistry.registerTool.mock
|
|
197
|
-
.calls[0][0];
|
|
198
|
-
expect(registeredTool.name).toBe('tool-stdio');
|
|
199
|
-
});
|
|
200
|
-
it('should discover tools via mcpServers config (sse)', async () => {
|
|
201
|
-
const serverConfig = { url: 'http://localhost:1234/sse' };
|
|
202
|
-
mockConfig.getMcpServers.mockReturnValue({ 'sse-server': serverConfig });
|
|
203
|
-
const mockTool = {
|
|
204
|
-
name: 'tool-sse',
|
|
205
|
-
description: 'desc-sse',
|
|
206
|
-
inputSchema: { type: 'object', properties: {} },
|
|
207
|
-
};
|
|
208
|
-
vi.mocked(Client.prototype.listTools).mockResolvedValue({
|
|
209
|
-
tools: [mockTool],
|
|
210
|
-
});
|
|
211
|
-
// PRE-MOCK getToolsByServer for the expected server name
|
|
212
|
-
mockToolRegistry.getToolsByServer.mockReturnValueOnce([
|
|
213
|
-
expect.any(DiscoveredMCPTool),
|
|
214
|
-
]);
|
|
215
|
-
await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
|
|
216
|
-
expect(SSEClientTransport).toHaveBeenCalledWith(new URL(serverConfig.url), {});
|
|
217
|
-
expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.any(DiscoveredMCPTool));
|
|
218
|
-
const registeredTool = mockToolRegistry.registerTool.mock
|
|
219
|
-
.calls[0][0];
|
|
220
|
-
expect(registeredTool.name).toBe('tool-sse');
|
|
221
|
-
});
|
|
222
|
-
describe('SseClientTransport headers', () => {
|
|
223
|
-
const setupSseTest = async (headers) => {
|
|
224
|
-
const serverConfig = {
|
|
225
|
-
url: 'http://localhost:1234/sse',
|
|
226
|
-
...(headers && { headers }),
|
|
227
|
-
};
|
|
228
|
-
const serverName = headers
|
|
229
|
-
? 'sse-server-with-headers'
|
|
230
|
-
: 'sse-server-no-headers';
|
|
231
|
-
const toolName = headers ? 'tool-http-headers' : 'tool-http-no-headers';
|
|
232
|
-
mockConfig.getMcpServers.mockReturnValue({ [serverName]: serverConfig });
|
|
233
|
-
const mockTool = {
|
|
234
|
-
name: toolName,
|
|
235
|
-
description: `desc-${toolName}`,
|
|
236
|
-
inputSchema: { type: 'object', properties: {} },
|
|
237
|
-
};
|
|
238
|
-
vi.mocked(Client.prototype.listTools).mockResolvedValue({
|
|
239
|
-
tools: [mockTool],
|
|
23
|
+
describe('discoverTools', () => {
|
|
24
|
+
it('should discover tools', async () => {
|
|
25
|
+
const mockedClient = {};
|
|
26
|
+
const mockedMcpToTool = vi.mocked(GenAiLib.mcpToTool).mockReturnValue({
|
|
27
|
+
tool: () => ({
|
|
28
|
+
functionDeclarations: [
|
|
29
|
+
{
|
|
30
|
+
name: 'testFunction',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
}),
|
|
240
34
|
});
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
it('should
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
it('should pass oauth token when provided', async () => {
|
|
260
|
-
const headers = {
|
|
261
|
-
Authorization: 'Bearer test-token',
|
|
262
|
-
};
|
|
263
|
-
const { serverConfig } = await setupSseTest(headers);
|
|
264
|
-
expect(SSEClientTransport).toHaveBeenCalledWith(new URL(serverConfig.url), { requestInit: { headers } });
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
it('should discover tools via mcpServers config (streamable http)', async () => {
|
|
268
|
-
const serverConfig = {
|
|
269
|
-
httpUrl: 'http://localhost:3000/mcp',
|
|
270
|
-
};
|
|
271
|
-
mockConfig.getMcpServers.mockReturnValue({ 'http-server': serverConfig });
|
|
272
|
-
const mockTool = {
|
|
273
|
-
name: 'tool-http',
|
|
274
|
-
description: 'desc-http',
|
|
275
|
-
inputSchema: { type: 'object', properties: {} },
|
|
276
|
-
};
|
|
277
|
-
vi.mocked(Client.prototype.listTools).mockResolvedValue({
|
|
278
|
-
tools: [mockTool],
|
|
279
|
-
});
|
|
280
|
-
mockToolRegistry.getToolsByServer.mockReturnValueOnce([
|
|
281
|
-
expect.any(DiscoveredMCPTool),
|
|
282
|
-
]);
|
|
283
|
-
await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
|
|
284
|
-
expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL(serverConfig.httpUrl), {});
|
|
285
|
-
expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.any(DiscoveredMCPTool));
|
|
286
|
-
const registeredTool = mockToolRegistry.registerTool.mock
|
|
287
|
-
.calls[0][0];
|
|
288
|
-
expect(registeredTool.name).toBe('tool-http');
|
|
289
|
-
});
|
|
290
|
-
describe('StreamableHTTPClientTransport headers', () => {
|
|
291
|
-
const setupHttpTest = async (headers) => {
|
|
292
|
-
const serverConfig = {
|
|
293
|
-
httpUrl: 'http://localhost:3000/mcp',
|
|
294
|
-
...(headers && { headers }),
|
|
295
|
-
};
|
|
296
|
-
const serverName = headers
|
|
297
|
-
? 'http-server-with-headers'
|
|
298
|
-
: 'http-server-no-headers';
|
|
299
|
-
const toolName = headers ? 'tool-http-headers' : 'tool-http-no-headers';
|
|
300
|
-
mockConfig.getMcpServers.mockReturnValue({ [serverName]: serverConfig });
|
|
301
|
-
const mockTool = {
|
|
302
|
-
name: toolName,
|
|
303
|
-
description: `desc-${toolName}`,
|
|
304
|
-
inputSchema: { type: 'object', properties: {} },
|
|
305
|
-
};
|
|
306
|
-
vi.mocked(Client.prototype.listTools).mockResolvedValue({
|
|
307
|
-
tools: [mockTool],
|
|
35
|
+
const tools = await discoverTools('test-server', {}, mockedClient);
|
|
36
|
+
expect(tools.length).toBe(1);
|
|
37
|
+
expect(mockedMcpToTool).toHaveBeenCalledOnce();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe('appendMcpServerCommand', () => {
|
|
41
|
+
it('should do nothing if no MCP servers or command are configured', () => {
|
|
42
|
+
const out = populateMcpServerCommand({}, undefined);
|
|
43
|
+
expect(out).toEqual({});
|
|
44
|
+
});
|
|
45
|
+
it('should discover tools via mcpServerCommand', () => {
|
|
46
|
+
const commandString = 'command --arg1 value1';
|
|
47
|
+
const out = populateMcpServerCommand({}, commandString);
|
|
48
|
+
expect(out).toEqual({
|
|
49
|
+
mcp: {
|
|
50
|
+
command: 'command',
|
|
51
|
+
args: ['--arg1', 'value1'],
|
|
52
|
+
},
|
|
308
53
|
});
|
|
309
|
-
mockToolRegistry.getToolsByServer.mockReturnValueOnce([
|
|
310
|
-
expect.any(DiscoveredMCPTool),
|
|
311
|
-
]);
|
|
312
|
-
await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
|
|
313
|
-
return { serverConfig };
|
|
314
|
-
};
|
|
315
|
-
it('should pass headers when provided', async () => {
|
|
316
|
-
const headers = {
|
|
317
|
-
Authorization: 'Bearer test-token',
|
|
318
|
-
'X-Custom-Header': 'custom-value',
|
|
319
|
-
};
|
|
320
|
-
const { serverConfig } = await setupHttpTest(headers);
|
|
321
|
-
expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL(serverConfig.httpUrl), { requestInit: { headers } });
|
|
322
54
|
});
|
|
323
|
-
it('should
|
|
324
|
-
|
|
325
|
-
expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL(serverConfig.httpUrl), {});
|
|
326
|
-
});
|
|
327
|
-
it('should pass oauth token when provided', async () => {
|
|
328
|
-
const headers = {
|
|
329
|
-
Authorization: 'Bearer test-token',
|
|
330
|
-
};
|
|
331
|
-
const { serverConfig } = await setupHttpTest(headers);
|
|
332
|
-
expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL(serverConfig.httpUrl), { requestInit: { headers } });
|
|
55
|
+
it('should handle error if mcpServerCommand parsing fails', () => {
|
|
56
|
+
expect(() => populateMcpServerCommand({}, 'derp && herp')).toThrowError();
|
|
333
57
|
});
|
|
334
58
|
});
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
// Store the original spy implementation if needed, or just let the new one be the behavior.
|
|
363
|
-
// The mockToolRegistry.registerTool is already a vi.fn() from mockToolRegistryInstance.
|
|
364
|
-
// We are setting its behavior for this test.
|
|
365
|
-
mockToolRegistry.registerTool.mockImplementation((toolToRegister) => {
|
|
366
|
-
// Simulate the actual registration name being stored for getTool to find
|
|
367
|
-
effectivelyRegisteredTools.set(toolToRegister.name, toolToRegister);
|
|
368
|
-
// If it's the first time toolA is registered (from server1, not prefixed),
|
|
369
|
-
// also make it findable by its original name for the prefixing check of server2/toolA.
|
|
370
|
-
if (toolToRegister.serverName === 'server1' &&
|
|
371
|
-
toolToRegister.serverToolName === 'toolA' &&
|
|
372
|
-
toolToRegister.name === 'toolA') {
|
|
373
|
-
effectivelyRegisteredTools.set('toolA', toolToRegister);
|
|
374
|
-
}
|
|
375
|
-
// The spy call count is inherently tracked by mockToolRegistry.registerTool itself.
|
|
59
|
+
describe('createTransport', () => {
|
|
60
|
+
const originalEnv = process.env;
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
vi.resetModules();
|
|
63
|
+
process.env = {};
|
|
64
|
+
});
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
process.env = originalEnv;
|
|
67
|
+
});
|
|
68
|
+
describe('should connect via httpUrl', () => {
|
|
69
|
+
it('without headers', async () => {
|
|
70
|
+
const transport = await createTransport('test-server', {
|
|
71
|
+
httpUrl: 'http://test-server',
|
|
72
|
+
}, false);
|
|
73
|
+
expect(transport).toEqual(new StreamableHTTPClientTransport(new URL('http://test-server'), {}));
|
|
74
|
+
});
|
|
75
|
+
it('with headers', async () => {
|
|
76
|
+
const transport = await createTransport('test-server', {
|
|
77
|
+
httpUrl: 'http://test-server',
|
|
78
|
+
headers: { Authorization: 'derp' },
|
|
79
|
+
}, false);
|
|
80
|
+
expect(transport).toEqual(new StreamableHTTPClientTransport(new URL('http://test-server'), {
|
|
81
|
+
requestInit: {
|
|
82
|
+
headers: { Authorization: 'derp' },
|
|
83
|
+
},
|
|
84
|
+
}));
|
|
85
|
+
});
|
|
376
86
|
});
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
87
|
+
describe('should connect via url', () => {
|
|
88
|
+
it('without headers', async () => {
|
|
89
|
+
const transport = await createTransport('test-server', {
|
|
90
|
+
url: 'http://test-server',
|
|
91
|
+
}, false);
|
|
92
|
+
expect(transport).toEqual(new SSEClientTransport(new URL('http://test-server'), {}));
|
|
93
|
+
});
|
|
94
|
+
it('with headers', async () => {
|
|
95
|
+
const transport = await createTransport('test-server', {
|
|
96
|
+
url: 'http://test-server',
|
|
97
|
+
headers: { Authorization: 'derp' },
|
|
98
|
+
}, false);
|
|
99
|
+
expect(transport).toEqual(new SSEClientTransport(new URL('http://test-server'), {
|
|
100
|
+
requestInit: {
|
|
101
|
+
headers: { Authorization: 'derp' },
|
|
102
|
+
},
|
|
103
|
+
}));
|
|
104
|
+
});
|
|
388
105
|
});
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
405
|
-
else {
|
|
406
|
-
expect(toolA_from_server1?.name).toBe('server1__toolA');
|
|
407
|
-
expect(toolA_from_server2?.name).toBe('toolA');
|
|
408
|
-
}
|
|
409
|
-
});
|
|
410
|
-
it('should clean schema properties ($schema, additionalProperties)', async () => {
|
|
411
|
-
const serverConfig = { command: './mcp-clean' };
|
|
412
|
-
mockConfig.getMcpServers.mockReturnValue({ 'clean-server': serverConfig });
|
|
413
|
-
const rawSchema = {
|
|
414
|
-
type: 'object',
|
|
415
|
-
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
416
|
-
additionalProperties: true,
|
|
417
|
-
properties: {
|
|
418
|
-
prop1: { type: 'string', $schema: 'remove-this' },
|
|
419
|
-
prop2: {
|
|
420
|
-
type: 'object',
|
|
421
|
-
additionalProperties: false,
|
|
422
|
-
properties: { nested: { type: 'number' } },
|
|
423
|
-
},
|
|
424
|
-
},
|
|
425
|
-
};
|
|
426
|
-
const mockTool = {
|
|
427
|
-
name: 'cleanTool',
|
|
428
|
-
description: 'd',
|
|
429
|
-
inputSchema: JSON.parse(JSON.stringify(rawSchema)),
|
|
430
|
-
};
|
|
431
|
-
vi.mocked(Client.prototype.listTools).mockResolvedValue({
|
|
432
|
-
tools: [mockTool],
|
|
106
|
+
it('should connect via command', async () => {
|
|
107
|
+
const mockedTransport = vi.mocked(SdkClientStdioLib.StdioClientTransport);
|
|
108
|
+
await createTransport('test-server', {
|
|
109
|
+
command: 'test-command',
|
|
110
|
+
args: ['--foo', 'bar'],
|
|
111
|
+
env: { FOO: 'bar' },
|
|
112
|
+
cwd: 'test/cwd',
|
|
113
|
+
}, false);
|
|
114
|
+
expect(mockedTransport).toHaveBeenCalledWith({
|
|
115
|
+
command: 'test-command',
|
|
116
|
+
args: ['--foo', 'bar'],
|
|
117
|
+
cwd: 'test/cwd',
|
|
118
|
+
env: { FOO: 'bar' },
|
|
119
|
+
stderr: 'pipe',
|
|
120
|
+
});
|
|
433
121
|
});
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
122
|
+
describe('useGoogleCredentialProvider', () => {
|
|
123
|
+
it('should use GoogleCredentialProvider when specified', async () => {
|
|
124
|
+
const transport = await createTransport('test-server', {
|
|
125
|
+
httpUrl: 'http://test-server',
|
|
126
|
+
authProviderType: AuthProviderType.GOOGLE_CREDENTIALS,
|
|
127
|
+
oauth: {
|
|
128
|
+
scopes: ['scope1'],
|
|
129
|
+
},
|
|
130
|
+
}, false);
|
|
131
|
+
expect(transport).toBeInstanceOf(StreamableHTTPClientTransport);
|
|
132
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
133
|
+
const authProvider = transport._authProvider;
|
|
134
|
+
expect(authProvider).toBeInstanceOf(GoogleCredentialProvider);
|
|
135
|
+
});
|
|
136
|
+
it('should use GoogleCredentialProvider with SSE transport', async () => {
|
|
137
|
+
const transport = await createTransport('test-server', {
|
|
138
|
+
url: 'http://test-server',
|
|
139
|
+
authProviderType: AuthProviderType.GOOGLE_CREDENTIALS,
|
|
140
|
+
oauth: {
|
|
141
|
+
scopes: ['scope1'],
|
|
142
|
+
},
|
|
143
|
+
}, false);
|
|
144
|
+
expect(transport).toBeInstanceOf(SSEClientTransport);
|
|
145
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
146
|
+
const authProvider = transport._authProvider;
|
|
147
|
+
expect(authProvider).toBeInstanceOf(GoogleCredentialProvider);
|
|
148
|
+
});
|
|
149
|
+
it('should throw an error if no URL is provided with GoogleCredentialProvider', async () => {
|
|
150
|
+
await expect(createTransport('test-server', {
|
|
151
|
+
authProviderType: AuthProviderType.GOOGLE_CREDENTIALS,
|
|
152
|
+
oauth: {
|
|
153
|
+
scopes: ['scope1'],
|
|
154
|
+
},
|
|
155
|
+
}, false)).rejects.toThrow('No URL configured for Google Credentials MCP server');
|
|
156
|
+
});
|
|
455
157
|
});
|
|
456
|
-
vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
457
|
-
await expect(discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry)).rejects.toThrow('Parsing failed');
|
|
458
|
-
expect(mockToolRegistry.registerTool).not.toHaveBeenCalled();
|
|
459
|
-
expect(console.error).not.toHaveBeenCalled();
|
|
460
|
-
});
|
|
461
|
-
it('should log error and skip server if config is invalid (missing url and command)', async () => {
|
|
462
|
-
mockConfig.getMcpServers.mockReturnValue({ 'bad-server': {} });
|
|
463
|
-
vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
464
|
-
await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
|
|
465
|
-
expect(console.error).toHaveBeenCalledWith(expect.stringContaining("MCP server 'bad-server' has invalid configuration"));
|
|
466
|
-
// Client constructor should not be called if config is invalid before instantiation
|
|
467
|
-
expect(Client).not.toHaveBeenCalled();
|
|
468
158
|
});
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
159
|
+
describe('isEnabled', () => {
|
|
160
|
+
const funcDecl = { name: 'myTool' };
|
|
161
|
+
const serverName = 'myServer';
|
|
162
|
+
it('should return true if no include or exclude lists are provided', () => {
|
|
163
|
+
const mcpServerConfig = {};
|
|
164
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(true);
|
|
473
165
|
});
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
expect(console.error).toHaveBeenCalledWith(expect.stringContaining("failed to start or connect to MCP server 'fail-connect-server'"));
|
|
478
|
-
expect(Client.prototype.listTools).not.toHaveBeenCalled();
|
|
479
|
-
expect(mockToolRegistry.registerTool).not.toHaveBeenCalled();
|
|
480
|
-
});
|
|
481
|
-
it('should log error and skip server if mcpClient.listTools fails', async () => {
|
|
482
|
-
const serverConfig = { command: './mcp-fail-list' };
|
|
483
|
-
mockConfig.getMcpServers.mockReturnValue({
|
|
484
|
-
'fail-list-server': serverConfig,
|
|
166
|
+
it('should return false if the tool is in the exclude list', () => {
|
|
167
|
+
const mcpServerConfig = { excludeTools: ['myTool'] };
|
|
168
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(false);
|
|
485
169
|
});
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
expect(console.error).toHaveBeenCalledWith(expect.stringContaining("Failed to list or register tools for MCP server 'fail-list-server'"));
|
|
490
|
-
expect(mockToolRegistry.registerTool).not.toHaveBeenCalled();
|
|
491
|
-
});
|
|
492
|
-
it('should assign mcpClient.onerror handler', async () => {
|
|
493
|
-
const serverConfig = { command: './mcp-onerror' };
|
|
494
|
-
mockConfig.getMcpServers.mockReturnValue({
|
|
495
|
-
'onerror-server': serverConfig,
|
|
170
|
+
it('should return true if the tool is in the include list', () => {
|
|
171
|
+
const mcpServerConfig = { includeTools: ['myTool'] };
|
|
172
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(true);
|
|
496
173
|
});
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
expect.
|
|
500
|
-
]);
|
|
501
|
-
await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
|
|
502
|
-
const clientInstances = vi.mocked(Client).mock.results;
|
|
503
|
-
expect(clientInstances.length).toBeGreaterThan(0);
|
|
504
|
-
const lastClientInstance = clientInstances[clientInstances.length - 1]?.value;
|
|
505
|
-
expect(lastClientInstance?.onerror).toEqual(expect.any(Function));
|
|
506
|
-
});
|
|
507
|
-
describe('Tool Filtering', () => {
|
|
508
|
-
const mockTools = [
|
|
509
|
-
{
|
|
510
|
-
name: 'toolA',
|
|
511
|
-
description: 'descA',
|
|
512
|
-
inputSchema: { type: 'object', properties: {} },
|
|
513
|
-
},
|
|
514
|
-
{
|
|
515
|
-
name: 'toolB',
|
|
516
|
-
description: 'descB',
|
|
517
|
-
inputSchema: { type: 'object', properties: {} },
|
|
518
|
-
},
|
|
519
|
-
{
|
|
520
|
-
name: 'toolC',
|
|
521
|
-
description: 'descC',
|
|
522
|
-
inputSchema: { type: 'object', properties: {} },
|
|
523
|
-
},
|
|
524
|
-
];
|
|
525
|
-
beforeEach(() => {
|
|
526
|
-
vi.mocked(Client.prototype.listTools).mockResolvedValue({
|
|
527
|
-
tools: mockTools,
|
|
528
|
-
});
|
|
529
|
-
mockToolRegistry.getToolsByServer.mockReturnValue([
|
|
530
|
-
expect.any(DiscoveredMCPTool),
|
|
531
|
-
]);
|
|
174
|
+
it('should return true if the tool is in the include list with parentheses', () => {
|
|
175
|
+
const mcpServerConfig = { includeTools: ['myTool()'] };
|
|
176
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(true);
|
|
532
177
|
});
|
|
533
|
-
it('should
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
includeTools: ['toolA', 'toolC'],
|
|
537
|
-
};
|
|
538
|
-
mockConfig.getMcpServers.mockReturnValue({
|
|
539
|
-
'include-server': serverConfig,
|
|
540
|
-
});
|
|
541
|
-
await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
|
|
542
|
-
expect(mockToolRegistry.registerTool).toHaveBeenCalledTimes(2);
|
|
543
|
-
expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolA' }));
|
|
544
|
-
expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolC' }));
|
|
545
|
-
expect(mockToolRegistry.registerTool).not.toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolB' }));
|
|
178
|
+
it('should return false if the include list exists but does not contain the tool', () => {
|
|
179
|
+
const mcpServerConfig = { includeTools: ['anotherTool'] };
|
|
180
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(false);
|
|
546
181
|
});
|
|
547
|
-
it('should
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
excludeTools: ['
|
|
182
|
+
it('should return false if the tool is in both the include and exclude lists', () => {
|
|
183
|
+
const mcpServerConfig = {
|
|
184
|
+
includeTools: ['myTool'],
|
|
185
|
+
excludeTools: ['myTool'],
|
|
551
186
|
};
|
|
552
|
-
|
|
553
|
-
'exclude-server': serverConfig,
|
|
554
|
-
});
|
|
555
|
-
await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
|
|
556
|
-
expect(mockToolRegistry.registerTool).toHaveBeenCalledTimes(2);
|
|
557
|
-
expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolA' }));
|
|
558
|
-
expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolC' }));
|
|
559
|
-
expect(mockToolRegistry.registerTool).not.toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolB' }));
|
|
187
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(false);
|
|
560
188
|
});
|
|
561
|
-
it('should
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
excludeTools: ['toolB'],
|
|
566
|
-
};
|
|
567
|
-
mockConfig.getMcpServers.mockReturnValue({ 'both-server': serverConfig });
|
|
568
|
-
await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
|
|
569
|
-
expect(mockToolRegistry.registerTool).toHaveBeenCalledTimes(1);
|
|
570
|
-
expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolA' }));
|
|
571
|
-
expect(mockToolRegistry.registerTool).not.toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolB' }));
|
|
572
|
-
expect(mockToolRegistry.registerTool).not.toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolC' }));
|
|
189
|
+
it('should return false if the function declaration has no name', () => {
|
|
190
|
+
const namelessFuncDecl = {};
|
|
191
|
+
const mcpServerConfig = {};
|
|
192
|
+
expect(isEnabled(namelessFuncDecl, serverName, mcpServerConfig)).toBe(false);
|
|
573
193
|
});
|
|
574
194
|
});
|
|
575
195
|
});
|
|
576
|
-
describe('sanitizeParameters', () => {
|
|
577
|
-
it('should do nothing for an undefined schema', () => {
|
|
578
|
-
const schema = undefined;
|
|
579
|
-
sanitizeParameters(schema);
|
|
580
|
-
});
|
|
581
|
-
it('should remove default when anyOf is present', () => {
|
|
582
|
-
const schema = {
|
|
583
|
-
anyOf: [{ type: Type.STRING }, { type: Type.NUMBER }],
|
|
584
|
-
default: 'hello',
|
|
585
|
-
};
|
|
586
|
-
sanitizeParameters(schema);
|
|
587
|
-
expect(schema.default).toBeUndefined();
|
|
588
|
-
});
|
|
589
|
-
it('should recursively sanitize items in anyOf', () => {
|
|
590
|
-
const schema = {
|
|
591
|
-
anyOf: [
|
|
592
|
-
{
|
|
593
|
-
anyOf: [{ type: Type.STRING }],
|
|
594
|
-
default: 'world',
|
|
595
|
-
},
|
|
596
|
-
{ type: Type.NUMBER },
|
|
597
|
-
],
|
|
598
|
-
};
|
|
599
|
-
sanitizeParameters(schema);
|
|
600
|
-
expect(schema.anyOf[0].default).toBeUndefined();
|
|
601
|
-
});
|
|
602
|
-
it('should recursively sanitize items in items', () => {
|
|
603
|
-
const schema = {
|
|
604
|
-
items: {
|
|
605
|
-
anyOf: [{ type: Type.STRING }],
|
|
606
|
-
default: 'world',
|
|
607
|
-
},
|
|
608
|
-
};
|
|
609
|
-
sanitizeParameters(schema);
|
|
610
|
-
expect(schema.items.default).toBeUndefined();
|
|
611
|
-
});
|
|
612
|
-
it('should recursively sanitize items in properties', () => {
|
|
613
|
-
const schema = {
|
|
614
|
-
properties: {
|
|
615
|
-
prop1: {
|
|
616
|
-
anyOf: [{ type: Type.STRING }],
|
|
617
|
-
default: 'world',
|
|
618
|
-
},
|
|
619
|
-
},
|
|
620
|
-
};
|
|
621
|
-
sanitizeParameters(schema);
|
|
622
|
-
expect(schema.properties.prop1.default).toBeUndefined();
|
|
623
|
-
});
|
|
624
|
-
it('should handle complex nested schemas', () => {
|
|
625
|
-
const schema = {
|
|
626
|
-
properties: {
|
|
627
|
-
prop1: {
|
|
628
|
-
items: {
|
|
629
|
-
anyOf: [{ type: Type.STRING }],
|
|
630
|
-
default: 'world',
|
|
631
|
-
},
|
|
632
|
-
},
|
|
633
|
-
prop2: {
|
|
634
|
-
anyOf: [
|
|
635
|
-
{
|
|
636
|
-
properties: {
|
|
637
|
-
nestedProp: {
|
|
638
|
-
anyOf: [{ type: Type.NUMBER }],
|
|
639
|
-
default: 123,
|
|
640
|
-
},
|
|
641
|
-
},
|
|
642
|
-
},
|
|
643
|
-
],
|
|
644
|
-
},
|
|
645
|
-
},
|
|
646
|
-
};
|
|
647
|
-
sanitizeParameters(schema);
|
|
648
|
-
expect(schema.properties.prop1.items.default).toBeUndefined();
|
|
649
|
-
const nestedProp = schema.properties.prop2.anyOf[0].properties.nestedProp;
|
|
650
|
-
expect(nestedProp?.default).toBeUndefined();
|
|
651
|
-
});
|
|
652
|
-
});
|
|
653
196
|
//# sourceMappingURL=mcp-client.test.js.map
|