@google/gemini-cli-core 0.1.12 → 0.1.13-nightly.250727.3e81359c
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 +107 -10
- 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 +55 -11
- package/dist/src/config/config.js +85 -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 +88 -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 +15 -0
- package/dist/src/index.js +17 -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/prompts/mcp-prompts.d.ts +8 -0
- package/dist/src/prompts/mcp-prompts.js +13 -0
- package/dist/src/prompts/mcp-prompts.js.map +1 -0
- package/dist/src/prompts/prompt-registry.d.ts +26 -0
- package/dist/src/prompts/prompt-registry.js +47 -0
- package/dist/src/prompts/prompt-registry.js.map +1 -0
- package/dist/src/services/fileDiscoveryService.test.js +101 -60
- package/dist/src/services/fileDiscoveryService.test.js.map +1 -1
- package/dist/src/services/gitService.js +1 -5
- package/dist/src/services/gitService.js.map +1 -1
- package/dist/src/services/gitService.test.js +68 -92
- 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/services/shellExecutionService.d.ts +70 -0
- package/dist/src/services/shellExecutionService.js +152 -0
- package/dist/src/services/shellExecutionService.js.map +1 -0
- package/dist/src/services/shellExecutionService.test.d.ts +6 -0
- package/dist/src/services/shellExecutionService.test.js +258 -0
- package/dist/src/services/shellExecutionService.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 +11 -7
- 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 +9 -6
- 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 +83 -1
- package/dist/src/tools/mcp-client.js +613 -148
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-client.test.js +211 -616
- 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/modifiable-tool.test.js +51 -62
- package/dist/src/tools/modifiable-tool.test.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 +3 -23
- package/dist/src/tools/shell.js +169 -293
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +255 -333
- 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/formatters.d.ts +6 -0
- package/dist/src/utils/formatters.js +16 -0
- package/dist/src/utils/formatters.js.map +1 -0
- 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/src/utils/textUtils.d.ts +13 -0
- package/dist/src/utils/textUtils.js +28 -0
- package/dist/src/utils/textUtils.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,246 @@
|
|
|
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, discoverPrompts, } 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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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',
|
|
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
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
const tools = await discoverTools('test-server', {}, mockedClient);
|
|
36
|
+
expect(tools.length).toBe(1);
|
|
37
|
+
expect(mockedMcpToTool).toHaveBeenCalledOnce();
|
|
194
38
|
});
|
|
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
39
|
});
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
const mockTool = {
|
|
204
|
-
name: 'tool-sse',
|
|
205
|
-
description: 'desc-sse',
|
|
206
|
-
inputSchema: { type: 'object', properties: {} },
|
|
40
|
+
describe('discoverPrompts', () => {
|
|
41
|
+
const mockedPromptRegistry = {
|
|
42
|
+
registerPrompt: vi.fn(),
|
|
207
43
|
};
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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],
|
|
44
|
+
it('should discover and log prompts', async () => {
|
|
45
|
+
const mockRequest = vi.fn().mockResolvedValue({
|
|
46
|
+
prompts: [
|
|
47
|
+
{ name: 'prompt1', description: 'desc1' },
|
|
48
|
+
{ name: 'prompt2' },
|
|
49
|
+
],
|
|
240
50
|
});
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
]);
|
|
244
|
-
await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
|
|
245
|
-
return { serverConfig };
|
|
246
|
-
};
|
|
247
|
-
it('should pass headers when provided', async () => {
|
|
248
|
-
const headers = {
|
|
249
|
-
Authorization: 'Bearer test-token',
|
|
250
|
-
'X-Custom-Header': 'custom-value',
|
|
51
|
+
const mockedClient = {
|
|
52
|
+
request: mockRequest,
|
|
251
53
|
};
|
|
252
|
-
|
|
253
|
-
expect(
|
|
254
|
-
});
|
|
255
|
-
it('should work without headers (backwards compatibility)', async () => {
|
|
256
|
-
const { serverConfig } = await setupSseTest();
|
|
257
|
-
expect(SSEClientTransport).toHaveBeenCalledWith(new URL(serverConfig.url), {});
|
|
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],
|
|
54
|
+
await discoverPrompts('test-server', mockedClient, mockedPromptRegistry);
|
|
55
|
+
expect(mockRequest).toHaveBeenCalledWith({ method: 'prompts/list', params: {} }, expect.anything());
|
|
279
56
|
});
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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: {} },
|
|
57
|
+
it('should do nothing if no prompts are discovered', async () => {
|
|
58
|
+
const mockRequest = vi.fn().mockResolvedValue({
|
|
59
|
+
prompts: [],
|
|
60
|
+
});
|
|
61
|
+
const mockedClient = {
|
|
62
|
+
request: mockRequest,
|
|
305
63
|
};
|
|
306
|
-
vi
|
|
307
|
-
|
|
64
|
+
const consoleLogSpy = vi
|
|
65
|
+
.spyOn(console, 'debug')
|
|
66
|
+
.mockImplementation(() => {
|
|
67
|
+
// no-op
|
|
308
68
|
});
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
69
|
+
await discoverPrompts('test-server', mockedClient, mockedPromptRegistry);
|
|
70
|
+
expect(mockRequest).toHaveBeenCalledOnce();
|
|
71
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
72
|
+
consoleLogSpy.mockRestore();
|
|
73
|
+
});
|
|
74
|
+
it('should log an error if discovery fails', async () => {
|
|
75
|
+
const testError = new Error('test error');
|
|
76
|
+
testError.message = 'test error';
|
|
77
|
+
const mockRequest = vi.fn().mockRejectedValue(testError);
|
|
78
|
+
const mockedClient = {
|
|
79
|
+
request: mockRequest,
|
|
319
80
|
};
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
81
|
+
const consoleErrorSpy = vi
|
|
82
|
+
.spyOn(console, 'error')
|
|
83
|
+
.mockImplementation(() => {
|
|
84
|
+
// no-op
|
|
85
|
+
});
|
|
86
|
+
await discoverPrompts('test-server', mockedClient, mockedPromptRegistry);
|
|
87
|
+
expect(mockRequest).toHaveBeenCalledOnce();
|
|
88
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(`Error discovering prompts from test-server: ${testError.message}`);
|
|
89
|
+
consoleErrorSpy.mockRestore();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('appendMcpServerCommand', () => {
|
|
93
|
+
it('should do nothing if no MCP servers or command are configured', () => {
|
|
94
|
+
const out = populateMcpServerCommand({}, undefined);
|
|
95
|
+
expect(out).toEqual({});
|
|
96
|
+
});
|
|
97
|
+
it('should discover tools via mcpServerCommand', () => {
|
|
98
|
+
const commandString = 'command --arg1 value1';
|
|
99
|
+
const out = populateMcpServerCommand({}, commandString);
|
|
100
|
+
expect(out).toEqual({
|
|
101
|
+
mcp: {
|
|
102
|
+
command: 'command',
|
|
103
|
+
args: ['--arg1', 'value1'],
|
|
104
|
+
},
|
|
105
|
+
});
|
|
326
106
|
});
|
|
327
|
-
it('should
|
|
328
|
-
|
|
329
|
-
Authorization: 'Bearer test-token',
|
|
330
|
-
};
|
|
331
|
-
const { serverConfig } = await setupHttpTest(headers);
|
|
332
|
-
expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL(serverConfig.httpUrl), { requestInit: { headers } });
|
|
107
|
+
it('should handle error if mcpServerCommand parsing fails', () => {
|
|
108
|
+
expect(() => populateMcpServerCommand({}, 'derp && herp')).toThrowError();
|
|
333
109
|
});
|
|
334
110
|
});
|
|
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.
|
|
111
|
+
describe('createTransport', () => {
|
|
112
|
+
const originalEnv = process.env;
|
|
113
|
+
beforeEach(() => {
|
|
114
|
+
vi.resetModules();
|
|
115
|
+
process.env = {};
|
|
116
|
+
});
|
|
117
|
+
afterEach(() => {
|
|
118
|
+
process.env = originalEnv;
|
|
119
|
+
});
|
|
120
|
+
describe('should connect via httpUrl', () => {
|
|
121
|
+
it('without headers', async () => {
|
|
122
|
+
const transport = await createTransport('test-server', {
|
|
123
|
+
httpUrl: 'http://test-server',
|
|
124
|
+
}, false);
|
|
125
|
+
expect(transport).toEqual(new StreamableHTTPClientTransport(new URL('http://test-server'), {}));
|
|
126
|
+
});
|
|
127
|
+
it('with headers', async () => {
|
|
128
|
+
const transport = await createTransport('test-server', {
|
|
129
|
+
httpUrl: 'http://test-server',
|
|
130
|
+
headers: { Authorization: 'derp' },
|
|
131
|
+
}, false);
|
|
132
|
+
expect(transport).toEqual(new StreamableHTTPClientTransport(new URL('http://test-server'), {
|
|
133
|
+
requestInit: {
|
|
134
|
+
headers: { Authorization: 'derp' },
|
|
135
|
+
},
|
|
136
|
+
}));
|
|
137
|
+
});
|
|
376
138
|
});
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
139
|
+
describe('should connect via url', () => {
|
|
140
|
+
it('without headers', async () => {
|
|
141
|
+
const transport = await createTransport('test-server', {
|
|
142
|
+
url: 'http://test-server',
|
|
143
|
+
}, false);
|
|
144
|
+
expect(transport).toEqual(new SSEClientTransport(new URL('http://test-server'), {}));
|
|
145
|
+
});
|
|
146
|
+
it('with headers', async () => {
|
|
147
|
+
const transport = await createTransport('test-server', {
|
|
148
|
+
url: 'http://test-server',
|
|
149
|
+
headers: { Authorization: 'derp' },
|
|
150
|
+
}, false);
|
|
151
|
+
expect(transport).toEqual(new SSEClientTransport(new URL('http://test-server'), {
|
|
152
|
+
requestInit: {
|
|
153
|
+
headers: { Authorization: 'derp' },
|
|
154
|
+
},
|
|
155
|
+
}));
|
|
156
|
+
});
|
|
388
157
|
});
|
|
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],
|
|
158
|
+
it('should connect via command', async () => {
|
|
159
|
+
const mockedTransport = vi.mocked(SdkClientStdioLib.StdioClientTransport);
|
|
160
|
+
await createTransport('test-server', {
|
|
161
|
+
command: 'test-command',
|
|
162
|
+
args: ['--foo', 'bar'],
|
|
163
|
+
env: { FOO: 'bar' },
|
|
164
|
+
cwd: 'test/cwd',
|
|
165
|
+
}, false);
|
|
166
|
+
expect(mockedTransport).toHaveBeenCalledWith({
|
|
167
|
+
command: 'test-command',
|
|
168
|
+
args: ['--foo', 'bar'],
|
|
169
|
+
cwd: 'test/cwd',
|
|
170
|
+
env: { FOO: 'bar' },
|
|
171
|
+
stderr: 'pipe',
|
|
172
|
+
});
|
|
433
173
|
});
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
174
|
+
describe('useGoogleCredentialProvider', () => {
|
|
175
|
+
it('should use GoogleCredentialProvider when specified', async () => {
|
|
176
|
+
const transport = await createTransport('test-server', {
|
|
177
|
+
httpUrl: 'http://test-server',
|
|
178
|
+
authProviderType: AuthProviderType.GOOGLE_CREDENTIALS,
|
|
179
|
+
oauth: {
|
|
180
|
+
scopes: ['scope1'],
|
|
181
|
+
},
|
|
182
|
+
}, false);
|
|
183
|
+
expect(transport).toBeInstanceOf(StreamableHTTPClientTransport);
|
|
184
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
185
|
+
const authProvider = transport._authProvider;
|
|
186
|
+
expect(authProvider).toBeInstanceOf(GoogleCredentialProvider);
|
|
187
|
+
});
|
|
188
|
+
it('should use GoogleCredentialProvider with SSE transport', async () => {
|
|
189
|
+
const transport = await createTransport('test-server', {
|
|
190
|
+
url: 'http://test-server',
|
|
191
|
+
authProviderType: AuthProviderType.GOOGLE_CREDENTIALS,
|
|
192
|
+
oauth: {
|
|
193
|
+
scopes: ['scope1'],
|
|
194
|
+
},
|
|
195
|
+
}, false);
|
|
196
|
+
expect(transport).toBeInstanceOf(SSEClientTransport);
|
|
197
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
198
|
+
const authProvider = transport._authProvider;
|
|
199
|
+
expect(authProvider).toBeInstanceOf(GoogleCredentialProvider);
|
|
200
|
+
});
|
|
201
|
+
it('should throw an error if no URL is provided with GoogleCredentialProvider', async () => {
|
|
202
|
+
await expect(createTransport('test-server', {
|
|
203
|
+
authProviderType: AuthProviderType.GOOGLE_CREDENTIALS,
|
|
204
|
+
oauth: {
|
|
205
|
+
scopes: ['scope1'],
|
|
206
|
+
},
|
|
207
|
+
}, false)).rejects.toThrow('No URL configured for Google Credentials MCP server');
|
|
208
|
+
});
|
|
455
209
|
});
|
|
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
210
|
});
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
211
|
+
describe('isEnabled', () => {
|
|
212
|
+
const funcDecl = { name: 'myTool' };
|
|
213
|
+
const serverName = 'myServer';
|
|
214
|
+
it('should return true if no include or exclude lists are provided', () => {
|
|
215
|
+
const mcpServerConfig = {};
|
|
216
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(true);
|
|
473
217
|
});
|
|
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,
|
|
218
|
+
it('should return false if the tool is in the exclude list', () => {
|
|
219
|
+
const mcpServerConfig = { excludeTools: ['myTool'] };
|
|
220
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(false);
|
|
485
221
|
});
|
|
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,
|
|
222
|
+
it('should return true if the tool is in the include list', () => {
|
|
223
|
+
const mcpServerConfig = { includeTools: ['myTool'] };
|
|
224
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(true);
|
|
496
225
|
});
|
|
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
|
-
]);
|
|
226
|
+
it('should return true if the tool is in the include list with parentheses', () => {
|
|
227
|
+
const mcpServerConfig = { includeTools: ['myTool()'] };
|
|
228
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(true);
|
|
532
229
|
});
|
|
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' }));
|
|
230
|
+
it('should return false if the include list exists but does not contain the tool', () => {
|
|
231
|
+
const mcpServerConfig = { includeTools: ['anotherTool'] };
|
|
232
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(false);
|
|
546
233
|
});
|
|
547
|
-
it('should
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
excludeTools: ['
|
|
234
|
+
it('should return false if the tool is in both the include and exclude lists', () => {
|
|
235
|
+
const mcpServerConfig = {
|
|
236
|
+
includeTools: ['myTool'],
|
|
237
|
+
excludeTools: ['myTool'],
|
|
551
238
|
};
|
|
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' }));
|
|
239
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(false);
|
|
560
240
|
});
|
|
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' }));
|
|
241
|
+
it('should return false if the function declaration has no name', () => {
|
|
242
|
+
const namelessFuncDecl = {};
|
|
243
|
+
const mcpServerConfig = {};
|
|
244
|
+
expect(isEnabled(namelessFuncDecl, serverName, mcpServerConfig)).toBe(false);
|
|
573
245
|
});
|
|
574
246
|
});
|
|
575
247
|
});
|
|
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
248
|
//# sourceMappingURL=mcp-client.test.js.map
|