@google/gemini-cli-core 0.1.12 → 0.1.13
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 +8 -2
- package/dist/google-gemini-cli-core-0.1.12.tgz +0 -0
- package/dist/src/code_assist/oauth2.js +39 -5
- package/dist/src/code_assist/oauth2.js.map +1 -1
- package/dist/src/code_assist/oauth2.test.js +8 -3
- package/dist/src/code_assist/oauth2.test.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/config/config.d.ts +28 -5
- package/dist/src/config/config.js +34 -18
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +28 -3
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/core/client.d.ts +3 -1
- package/dist/src/core/client.js +38 -1
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +94 -37
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/contentGenerator.d.ts +2 -1
- package/dist/src/core/contentGenerator.js +4 -3
- 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/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 +3 -0
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
- package/dist/src/core/turn.d.ts +6 -2
- package/dist/src/core/turn.js +1 -0
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.js +8 -0
- package/dist/src/index.js.map +1 -1
- 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/ideContext.d.ts +126 -0
- package/dist/src/services/ideContext.js +98 -0
- package/dist/src/services/ideContext.js.map +1 -0
- package/dist/src/services/ideContext.test.d.ts +6 -0
- package/dist/src/services/ideContext.test.js +111 -0
- package/dist/src/services/ideContext.test.js.map +1 -0
- package/dist/src/services/loopDetectionService.d.ts +51 -0
- package/dist/src/services/loopDetectionService.js +232 -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 +339 -0
- package/dist/src/services/loopDetectionService.test.js.map +1 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +4 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +54 -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/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 +2 -1
- package/dist/src/telemetry/loggers.js +17 -1
- 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/types.d.ts +12 -1
- package/dist/src/telemetry/types.js +16 -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 +24 -28
- package/dist/src/tools/edit.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 +4 -1
- package/dist/src/tools/grep.test.js.map +1 -1
- package/dist/src/tools/ls.d.ts +1 -12
- package/dist/src/tools/ls.js +8 -30
- package/dist/src/tools/ls.js.map +1 -1
- package/dist/src/tools/mcp-client.d.ts +55 -1
- package/dist/src/tools/mcp-client.js +165 -140
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-client.test.js +127 -624
- 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 +33 -9
- package/dist/src/tools/mcp-tool.js.map +1 -1
- package/dist/src/tools/mcp-tool.test.js +40 -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 +2 -1
- package/dist/src/tools/read-file.test.js.map +1 -1
- package/dist/src/tools/read-many-files.d.ts +1 -7
- package/dist/src/tools/read-many-files.js +12 -21
- package/dist/src/tools/read-many-files.js.map +1 -1
- package/dist/src/tools/read-many-files.test.js +2 -1
- package/dist/src/tools/read-many-files.test.js.map +1 -1
- package/dist/src/tools/shell.js +11 -4
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +60 -0
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/tool-registry.d.ts +0 -1
- package/dist/src/tools/tool-registry.js +12 -9
- package/dist/src/tools/tool-registry.js.map +1 -1
- package/dist/src/tools/tool-registry.test.js +109 -38
- 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/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/errors.js +4 -4
- package/dist/src/utils/errors.js.map +1 -1
- package/dist/src/utils/fileUtils.js +2 -2
- package/dist/src/utils/fileUtils.js.map +1 -1
- package/dist/src/utils/quotaErrorDetection.js +2 -9
- package/dist/src/utils/quotaErrorDetection.js.map +1 -1
- package/dist/src/utils/retry.d.ts +6 -0
- package/dist/src/utils/retry.js +1 -1
- 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/summarizer.d.ts +1 -1
- package/dist/src/utils/summarizer.js +10 -9
- package/dist/src/utils/summarizer.js.map +1 -1
- package/dist/src/utils/summarizer.test.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/dist/google-gemini-cli-core-0.1.11.tgz +0 -0
|
@@ -3,651 +3,154 @@
|
|
|
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
|
-
MockedClient.mockImplementation(() => ({
|
|
25
|
-
connect: MockedClient.prototype.connect,
|
|
26
|
-
listTools: MockedClient.prototype.listTools,
|
|
27
|
-
onerror: vi.fn(), // Each instance gets its own onerror mock
|
|
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
|
+
vi.mock('@modelcontextprotocol/sdk/client/stdio.js');
|
|
13
|
+
vi.mock('@modelcontextprotocol/sdk/client/index.js');
|
|
14
|
+
vi.mock('@google/genai');
|
|
15
|
+
describe('mcp-client', () => {
|
|
125
16
|
afterEach(() => {
|
|
126
17
|
vi.restoreAllMocks();
|
|
127
18
|
});
|
|
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],
|
|
19
|
+
describe('discoverTools', () => {
|
|
20
|
+
it('should discover tools', async () => {
|
|
21
|
+
const mockedClient = {};
|
|
22
|
+
const mockedMcpToTool = vi.mocked(GenAiLib.mcpToTool).mockReturnValue({
|
|
23
|
+
tool: () => ({
|
|
24
|
+
functionDeclarations: [
|
|
25
|
+
{
|
|
26
|
+
name: 'testFunction',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
}),
|
|
240
30
|
});
|
|
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],
|
|
31
|
+
const tools = await discoverTools('test-server', {}, mockedClient);
|
|
32
|
+
expect(tools.length).toBe(1);
|
|
33
|
+
expect(mockedMcpToTool).toHaveBeenCalledOnce();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
describe('appendMcpServerCommand', () => {
|
|
37
|
+
it('should do nothing if no MCP servers or command are configured', () => {
|
|
38
|
+
const out = populateMcpServerCommand({}, undefined);
|
|
39
|
+
expect(out).toEqual({});
|
|
40
|
+
});
|
|
41
|
+
it('should discover tools via mcpServerCommand', () => {
|
|
42
|
+
const commandString = 'command --arg1 value1';
|
|
43
|
+
const out = populateMcpServerCommand({}, commandString);
|
|
44
|
+
expect(out).toEqual({
|
|
45
|
+
mcp: {
|
|
46
|
+
command: 'command',
|
|
47
|
+
args: ['--arg1', 'value1'],
|
|
48
|
+
},
|
|
308
49
|
});
|
|
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
50
|
});
|
|
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 } });
|
|
51
|
+
it('should handle error if mcpServerCommand parsing fails', () => {
|
|
52
|
+
expect(() => populateMcpServerCommand({}, 'derp && herp')).toThrowError();
|
|
333
53
|
});
|
|
334
54
|
});
|
|
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.
|
|
376
|
-
});
|
|
377
|
-
// PRE-MOCK getToolsByServer for the expected server names
|
|
378
|
-
// This is for the final check in connectAndDiscover to see if any tools were registered *from that server*
|
|
379
|
-
mockToolRegistry.getToolsByServer.mockImplementation((serverName) => {
|
|
380
|
-
if (serverName === 'server1')
|
|
381
|
-
return [
|
|
382
|
-
expect.objectContaining({ name: 'toolA' }),
|
|
383
|
-
expect.objectContaining({ name: 'toolB' }),
|
|
384
|
-
];
|
|
385
|
-
if (serverName === 'server2')
|
|
386
|
-
return [expect.objectContaining({ name: 'server2__toolA' })];
|
|
387
|
-
return [];
|
|
55
|
+
describe('createTransport', () => {
|
|
56
|
+
const originalEnv = process.env;
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
vi.resetModules();
|
|
59
|
+
process.env = {};
|
|
60
|
+
});
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
process.env = originalEnv;
|
|
63
|
+
});
|
|
64
|
+
describe('should connect via httpUrl', () => {
|
|
65
|
+
it('without headers', async () => {
|
|
66
|
+
const transport = createTransport('test-server', {
|
|
67
|
+
httpUrl: 'http://test-server',
|
|
68
|
+
}, false);
|
|
69
|
+
expect(transport).toEqual(new StreamableHTTPClientTransport(new URL('http://test-server'), {}));
|
|
70
|
+
});
|
|
71
|
+
it('with headers', async () => {
|
|
72
|
+
const transport = createTransport('test-server', {
|
|
73
|
+
httpUrl: 'http://test-server',
|
|
74
|
+
headers: { Authorization: 'derp' },
|
|
75
|
+
}, false);
|
|
76
|
+
expect(transport).toEqual(new StreamableHTTPClientTransport(new URL('http://test-server'), {
|
|
77
|
+
requestInit: {
|
|
78
|
+
headers: { Authorization: 'derp' },
|
|
79
|
+
},
|
|
80
|
+
}));
|
|
81
|
+
});
|
|
388
82
|
});
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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],
|
|
83
|
+
describe('should connect via url', () => {
|
|
84
|
+
it('without headers', async () => {
|
|
85
|
+
const transport = createTransport('test-server', {
|
|
86
|
+
url: 'http://test-server',
|
|
87
|
+
}, false);
|
|
88
|
+
expect(transport).toEqual(new SSEClientTransport(new URL('http://test-server'), {}));
|
|
89
|
+
});
|
|
90
|
+
it('with headers', async () => {
|
|
91
|
+
const transport = createTransport('test-server', {
|
|
92
|
+
url: 'http://test-server',
|
|
93
|
+
headers: { Authorization: 'derp' },
|
|
94
|
+
}, false);
|
|
95
|
+
expect(transport).toEqual(new SSEClientTransport(new URL('http://test-server'), {
|
|
96
|
+
requestInit: {
|
|
97
|
+
headers: { Authorization: 'derp' },
|
|
98
|
+
},
|
|
99
|
+
}));
|
|
100
|
+
});
|
|
433
101
|
});
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
});
|
|
450
|
-
it('should handle error if mcpServerCommand parsing fails', async () => {
|
|
451
|
-
const commandString = 'my-mcp-server "unterminated quote';
|
|
452
|
-
mockConfig.getMcpServerCommand.mockReturnValue(commandString);
|
|
453
|
-
vi.mocked(parse).mockImplementation(() => {
|
|
454
|
-
throw new Error('Parsing failed');
|
|
102
|
+
it('should connect via command', () => {
|
|
103
|
+
const mockedTransport = vi.mocked(SdkClientStdioLib.StdioClientTransport);
|
|
104
|
+
createTransport('test-server', {
|
|
105
|
+
command: 'test-command',
|
|
106
|
+
args: ['--foo', 'bar'],
|
|
107
|
+
env: { FOO: 'bar' },
|
|
108
|
+
cwd: 'test/cwd',
|
|
109
|
+
}, false);
|
|
110
|
+
expect(mockedTransport).toHaveBeenCalledWith({
|
|
111
|
+
command: 'test-command',
|
|
112
|
+
args: ['--foo', 'bar'],
|
|
113
|
+
cwd: 'test/cwd',
|
|
114
|
+
env: { FOO: 'bar' },
|
|
115
|
+
stderr: 'pipe',
|
|
116
|
+
});
|
|
455
117
|
});
|
|
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
118
|
});
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
expect(Client).not.toHaveBeenCalled();
|
|
468
|
-
});
|
|
469
|
-
it('should log error and skip server if mcpClient.connect fails', async () => {
|
|
470
|
-
const serverConfig = { command: './mcp-fail-connect' };
|
|
471
|
-
mockConfig.getMcpServers.mockReturnValue({
|
|
472
|
-
'fail-connect-server': serverConfig,
|
|
119
|
+
describe('isEnabled', () => {
|
|
120
|
+
const funcDecl = { name: 'myTool' };
|
|
121
|
+
const serverName = 'myServer';
|
|
122
|
+
it('should return true if no include or exclude lists are provided', () => {
|
|
123
|
+
const mcpServerConfig = {};
|
|
124
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(true);
|
|
473
125
|
});
|
|
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,
|
|
126
|
+
it('should return false if the tool is in the exclude list', () => {
|
|
127
|
+
const mcpServerConfig = { excludeTools: ['myTool'] };
|
|
128
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(false);
|
|
485
129
|
});
|
|
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,
|
|
130
|
+
it('should return true if the tool is in the include list', () => {
|
|
131
|
+
const mcpServerConfig = { includeTools: ['myTool'] };
|
|
132
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(true);
|
|
496
133
|
});
|
|
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
|
-
]);
|
|
134
|
+
it('should return true if the tool is in the include list with parentheses', () => {
|
|
135
|
+
const mcpServerConfig = { includeTools: ['myTool()'] };
|
|
136
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(true);
|
|
532
137
|
});
|
|
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' }));
|
|
138
|
+
it('should return false if the include list exists but does not contain the tool', () => {
|
|
139
|
+
const mcpServerConfig = { includeTools: ['anotherTool'] };
|
|
140
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(false);
|
|
546
141
|
});
|
|
547
|
-
it('should
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
excludeTools: ['
|
|
142
|
+
it('should return false if the tool is in both the include and exclude lists', () => {
|
|
143
|
+
const mcpServerConfig = {
|
|
144
|
+
includeTools: ['myTool'],
|
|
145
|
+
excludeTools: ['myTool'],
|
|
551
146
|
};
|
|
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' }));
|
|
147
|
+
expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(false);
|
|
560
148
|
});
|
|
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' }));
|
|
149
|
+
it('should return false if the function declaration has no name', () => {
|
|
150
|
+
const namelessFuncDecl = {};
|
|
151
|
+
const mcpServerConfig = {};
|
|
152
|
+
expect(isEnabled(namelessFuncDecl, serverName, mcpServerConfig)).toBe(false);
|
|
573
153
|
});
|
|
574
154
|
});
|
|
575
155
|
});
|
|
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
156
|
//# sourceMappingURL=mcp-client.test.js.map
|