mcp-rubber-duck 1.5.1 → 1.5.2
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/.releaserc.json +4 -0
- package/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/tests/approval.test.ts +440 -0
- package/tests/cache.test.ts +240 -0
- package/tests/config.test.ts +468 -0
- package/tests/consensus.test.ts +10 -0
- package/tests/conversation.test.ts +86 -0
- package/tests/duck-debate.test.ts +105 -1
- package/tests/duck-iterate.test.ts +30 -0
- package/tests/duck-judge.test.ts +93 -0
- package/tests/duck-vote.test.ts +46 -0
- package/tests/health.test.ts +129 -0
- package/tests/providers.test.ts +591 -0
- package/tests/safe-logger.test.ts +314 -0
- package/tests/tools/approve-mcp-request.test.ts +239 -0
- package/tests/tools/ask-duck.test.ts +159 -0
- package/tests/tools/chat-duck.test.ts +191 -0
- package/tests/tools/compare-ducks.test.ts +190 -0
- package/tests/tools/duck-council.test.ts +219 -0
- package/tests/tools/get-pending-approvals.test.ts +195 -0
- package/tests/tools/list-ducks.test.ts +144 -0
- package/tests/tools/list-models.test.ts +163 -0
- package/tests/tools/mcp-status.test.ts +330 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
|
|
2
|
+
import { listModelsTool } from '../../src/tools/list-models.js';
|
|
3
|
+
import { ProviderManager } from '../../src/providers/manager.js';
|
|
4
|
+
|
|
5
|
+
// Mock dependencies
|
|
6
|
+
jest.mock('../../src/utils/logger');
|
|
7
|
+
jest.mock('../../src/providers/manager.js');
|
|
8
|
+
|
|
9
|
+
describe('listModelsTool', () => {
|
|
10
|
+
let mockProviderManager: jest.Mocked<ProviderManager>;
|
|
11
|
+
|
|
12
|
+
const mockProviders = [
|
|
13
|
+
{
|
|
14
|
+
name: 'openai',
|
|
15
|
+
info: {
|
|
16
|
+
nickname: 'OpenAI Duck',
|
|
17
|
+
model: 'gpt-4',
|
|
18
|
+
baseURL: 'https://api.openai.com/v1',
|
|
19
|
+
hasApiKey: true,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'groq',
|
|
24
|
+
info: {
|
|
25
|
+
nickname: 'Groq Duck',
|
|
26
|
+
model: 'llama-3.1-70b-versatile',
|
|
27
|
+
baseURL: 'https://api.groq.com/openai/v1',
|
|
28
|
+
hasApiKey: true,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const mockOpenAIModels = [
|
|
34
|
+
{ id: 'gpt-4', description: 'Most capable model', context_window: 8192 },
|
|
35
|
+
{ id: 'gpt-3.5-turbo', owned_by: 'openai', context_window: 4096 },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const mockGroqModels = [
|
|
39
|
+
{ id: 'llama-3.1-70b-versatile', description: 'Versatile large model' },
|
|
40
|
+
{ id: 'llama-3.1-8b-instant', description: 'Fast small model' },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
mockProviderManager = {
|
|
45
|
+
getAllProviders: jest.fn().mockReturnValue(mockProviders),
|
|
46
|
+
getAvailableModels: jest.fn().mockImplementation((provider) => {
|
|
47
|
+
if (provider === 'openai') return Promise.resolve(mockOpenAIModels);
|
|
48
|
+
if (provider === 'groq') return Promise.resolve(mockGroqModels);
|
|
49
|
+
return Promise.resolve([]);
|
|
50
|
+
}),
|
|
51
|
+
} as unknown as jest.Mocked<ProviderManager>;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should list models for all providers by default', async () => {
|
|
55
|
+
const result = await listModelsTool(mockProviderManager, {});
|
|
56
|
+
|
|
57
|
+
expect(mockProviderManager.getAllProviders).toHaveBeenCalled();
|
|
58
|
+
expect(mockProviderManager.getAvailableModels).toHaveBeenCalledTimes(2);
|
|
59
|
+
expect(result.content[0].text).toContain('OpenAI Duck');
|
|
60
|
+
expect(result.content[0].text).toContain('Groq Duck');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should list models for specific provider', async () => {
|
|
64
|
+
const result = await listModelsTool(mockProviderManager, {
|
|
65
|
+
provider: 'openai',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(mockProviderManager.getAvailableModels).toHaveBeenCalledWith('openai');
|
|
69
|
+
expect(mockProviderManager.getAvailableModels).toHaveBeenCalledTimes(1);
|
|
70
|
+
expect(result.content[0].text).toContain('OpenAI Duck');
|
|
71
|
+
expect(result.content[0].text).not.toContain('Groq Duck');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should throw error for unknown provider', async () => {
|
|
75
|
+
await expect(
|
|
76
|
+
listModelsTool(mockProviderManager, { provider: 'unknown' })
|
|
77
|
+
).rejects.toThrow('Provider "unknown" not found');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should display model details', async () => {
|
|
81
|
+
const result = await listModelsTool(mockProviderManager, {
|
|
82
|
+
provider: 'openai',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(result.content[0].text).toContain('gpt-4');
|
|
86
|
+
expect(result.content[0].text).toContain('Most capable model');
|
|
87
|
+
expect(result.content[0].text).toContain('8192 tokens');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should mark default model', async () => {
|
|
91
|
+
const result = await listModelsTool(mockProviderManager, {
|
|
92
|
+
provider: 'openai',
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(result.content[0].text).toContain('gpt-4');
|
|
96
|
+
expect(result.content[0].text).toContain('(default)');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should display owned_by when no description', async () => {
|
|
100
|
+
const result = await listModelsTool(mockProviderManager, {
|
|
101
|
+
provider: 'openai',
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(result.content[0].text).toContain('by openai');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should handle empty models list', async () => {
|
|
108
|
+
mockProviderManager.getAvailableModels.mockResolvedValue([]);
|
|
109
|
+
|
|
110
|
+
const result = await listModelsTool(mockProviderManager, {
|
|
111
|
+
provider: 'openai',
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(result.content[0].text).toContain('No models available');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should handle provider errors gracefully', async () => {
|
|
118
|
+
mockProviderManager.getAvailableModels.mockImplementation((provider) => {
|
|
119
|
+
if (provider === 'openai') return Promise.resolve(mockOpenAIModels);
|
|
120
|
+
return Promise.reject(new Error('Connection failed'));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const result = await listModelsTool(mockProviderManager, {});
|
|
124
|
+
|
|
125
|
+
expect(result.content[0].text).toContain('OpenAI Duck');
|
|
126
|
+
expect(result.content[0].text).toContain('Failed to fetch models');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should show cached indicator when not fetching latest', async () => {
|
|
130
|
+
const result = await listModelsTool(mockProviderManager, {
|
|
131
|
+
fetch_latest: false,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(result.content[0].text).toContain('Using cached/configured models');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should show fetch indicator when fetching latest', async () => {
|
|
138
|
+
const result = await listModelsTool(mockProviderManager, {
|
|
139
|
+
fetch_latest: true,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(result.content[0].text).toContain('Fetched from API');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should display context window when available', async () => {
|
|
146
|
+
const result = await listModelsTool(mockProviderManager, {
|
|
147
|
+
provider: 'openai',
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
expect(result.content[0].text).toContain('8192 tokens');
|
|
151
|
+
expect(result.content[0].text).toContain('4096 tokens');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should handle models without context window', async () => {
|
|
155
|
+
const result = await listModelsTool(mockProviderManager, {
|
|
156
|
+
provider: 'groq',
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Groq models don't have context_window in mock
|
|
160
|
+
expect(result.content[0].text).toContain('llama-3.1-70b-versatile');
|
|
161
|
+
expect(result.content[0].text).not.toContain('tokens]');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
|
|
2
|
+
import { mcpStatusTool } from '../../src/tools/mcp-status.js';
|
|
3
|
+
import { MCPClientManager } from '../../src/services/mcp-client-manager.js';
|
|
4
|
+
import { ApprovalService } from '../../src/services/approval.js';
|
|
5
|
+
import { FunctionBridge } from '../../src/services/function-bridge.js';
|
|
6
|
+
|
|
7
|
+
// Mock dependencies
|
|
8
|
+
jest.mock('../../src/utils/logger');
|
|
9
|
+
jest.mock('../../src/services/mcp-client-manager.js');
|
|
10
|
+
jest.mock('../../src/services/approval.js');
|
|
11
|
+
jest.mock('../../src/services/function-bridge.js');
|
|
12
|
+
|
|
13
|
+
describe('mcpStatusTool', () => {
|
|
14
|
+
let mockMcpManager: jest.Mocked<MCPClientManager>;
|
|
15
|
+
let mockApprovalService: jest.Mocked<ApprovalService>;
|
|
16
|
+
let mockFunctionBridge: jest.Mocked<FunctionBridge>;
|
|
17
|
+
|
|
18
|
+
const mockServerStatus = {
|
|
19
|
+
filesystem: {
|
|
20
|
+
type: 'stdio',
|
|
21
|
+
status: 'connected',
|
|
22
|
+
},
|
|
23
|
+
database: {
|
|
24
|
+
type: 'sse',
|
|
25
|
+
status: 'connecting',
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const mockTools = [
|
|
30
|
+
{ name: 'read_file', serverName: 'filesystem' },
|
|
31
|
+
{ name: 'write_file', serverName: 'filesystem' },
|
|
32
|
+
{ name: 'query', serverName: 'database' },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const mockApprovalStats = {
|
|
36
|
+
total: 10,
|
|
37
|
+
pending: 2,
|
|
38
|
+
approved: 5,
|
|
39
|
+
denied: 2,
|
|
40
|
+
expired: 1,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const mockBridgeStats = {
|
|
44
|
+
trustedToolCount: 5,
|
|
45
|
+
totalCalls: 100,
|
|
46
|
+
successfulCalls: 95,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
mockMcpManager = {
|
|
51
|
+
getStatus: jest.fn().mockReturnValue(mockServerStatus),
|
|
52
|
+
getConnectedServers: jest.fn().mockReturnValue(['filesystem', 'database']),
|
|
53
|
+
listAllTools: jest.fn().mockResolvedValue(mockTools),
|
|
54
|
+
} as unknown as jest.Mocked<MCPClientManager>;
|
|
55
|
+
|
|
56
|
+
mockApprovalService = {
|
|
57
|
+
getStats: jest.fn().mockReturnValue(mockApprovalStats),
|
|
58
|
+
getPendingApprovals: jest.fn().mockReturnValue([]),
|
|
59
|
+
} as unknown as jest.Mocked<ApprovalService>;
|
|
60
|
+
|
|
61
|
+
mockFunctionBridge = {
|
|
62
|
+
getStats: jest.fn().mockReturnValue(mockBridgeStats),
|
|
63
|
+
} as unknown as jest.Mocked<FunctionBridge>;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('overview section', () => {
|
|
67
|
+
it('should display MCP Bridge status header', async () => {
|
|
68
|
+
const result = await mcpStatusTool(
|
|
69
|
+
mockMcpManager,
|
|
70
|
+
mockApprovalService,
|
|
71
|
+
mockFunctionBridge,
|
|
72
|
+
{}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(result.content[0].text).toContain('MCP Bridge Status');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should display connected servers count', async () => {
|
|
79
|
+
const result = await mcpStatusTool(
|
|
80
|
+
mockMcpManager,
|
|
81
|
+
mockApprovalService,
|
|
82
|
+
mockFunctionBridge,
|
|
83
|
+
{}
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
expect(result.content[0].text).toContain('Connected Servers: 2');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should display available tools count', async () => {
|
|
90
|
+
const result = await mcpStatusTool(
|
|
91
|
+
mockMcpManager,
|
|
92
|
+
mockApprovalService,
|
|
93
|
+
mockFunctionBridge,
|
|
94
|
+
{}
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
expect(result.content[0].text).toContain('Available Tools: 3');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should display trusted tools count', async () => {
|
|
101
|
+
const result = await mcpStatusTool(
|
|
102
|
+
mockMcpManager,
|
|
103
|
+
mockApprovalService,
|
|
104
|
+
mockFunctionBridge,
|
|
105
|
+
{}
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(result.content[0].text).toContain('Trusted Tools: 5');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('server details section', () => {
|
|
113
|
+
it('should display server status with icons', async () => {
|
|
114
|
+
const result = await mcpStatusTool(
|
|
115
|
+
mockMcpManager,
|
|
116
|
+
mockApprovalService,
|
|
117
|
+
mockFunctionBridge,
|
|
118
|
+
{}
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
expect(result.content[0].text).toContain('🟢'); // connected
|
|
122
|
+
expect(result.content[0].text).toContain('🟡'); // connecting
|
|
123
|
+
expect(result.content[0].text).toContain('filesystem');
|
|
124
|
+
expect(result.content[0].text).toContain('database');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should display server type and status', async () => {
|
|
128
|
+
const result = await mcpStatusTool(
|
|
129
|
+
mockMcpManager,
|
|
130
|
+
mockApprovalService,
|
|
131
|
+
mockFunctionBridge,
|
|
132
|
+
{}
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
expect(result.content[0].text).toContain('stdio');
|
|
136
|
+
expect(result.content[0].text).toContain('Status: connected');
|
|
137
|
+
expect(result.content[0].text).toContain('Status: connecting');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should display tool count per server', async () => {
|
|
141
|
+
const result = await mcpStatusTool(
|
|
142
|
+
mockMcpManager,
|
|
143
|
+
mockApprovalService,
|
|
144
|
+
mockFunctionBridge,
|
|
145
|
+
{}
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
expect(result.content[0].text).toContain('Tools: 2'); // filesystem
|
|
149
|
+
expect(result.content[0].text).toContain('Tools: 1'); // database
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should display tool names for small tool lists', async () => {
|
|
153
|
+
const result = await mcpStatusTool(
|
|
154
|
+
mockMcpManager,
|
|
155
|
+
mockApprovalService,
|
|
156
|
+
mockFunctionBridge,
|
|
157
|
+
{}
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
expect(result.content[0].text).toContain('read_file');
|
|
161
|
+
expect(result.content[0].text).toContain('write_file');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should truncate long tool lists with count', async () => {
|
|
165
|
+
mockMcpManager.listAllTools.mockResolvedValue([
|
|
166
|
+
{ name: 'tool1', serverName: 'server1' },
|
|
167
|
+
{ name: 'tool2', serverName: 'server1' },
|
|
168
|
+
{ name: 'tool3', serverName: 'server1' },
|
|
169
|
+
{ name: 'tool4', serverName: 'server1' },
|
|
170
|
+
{ name: 'tool5', serverName: 'server1' },
|
|
171
|
+
]);
|
|
172
|
+
mockMcpManager.getStatus.mockReturnValue({
|
|
173
|
+
server1: { type: 'stdio', status: 'connected' },
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const result = await mcpStatusTool(
|
|
177
|
+
mockMcpManager,
|
|
178
|
+
mockApprovalService,
|
|
179
|
+
mockFunctionBridge,
|
|
180
|
+
{}
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
expect(result.content[0].text).toContain('+2 more');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should handle disconnected server', async () => {
|
|
187
|
+
mockMcpManager.getStatus.mockReturnValue({
|
|
188
|
+
broken: { type: 'stdio', status: 'disconnected' },
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const result = await mcpStatusTool(
|
|
192
|
+
mockMcpManager,
|
|
193
|
+
mockApprovalService,
|
|
194
|
+
mockFunctionBridge,
|
|
195
|
+
{}
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
expect(result.content[0].text).toContain('🔴');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should show message when no servers configured', async () => {
|
|
202
|
+
mockMcpManager.getStatus.mockReturnValue({});
|
|
203
|
+
mockMcpManager.listAllTools.mockResolvedValue([]);
|
|
204
|
+
mockMcpManager.getConnectedServers.mockReturnValue([]);
|
|
205
|
+
|
|
206
|
+
const result = await mcpStatusTool(
|
|
207
|
+
mockMcpManager,
|
|
208
|
+
mockApprovalService,
|
|
209
|
+
mockFunctionBridge,
|
|
210
|
+
{}
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
expect(result.content[0].text).toContain('No MCP servers configured');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('approval statistics section', () => {
|
|
218
|
+
it('should display all approval statistics', async () => {
|
|
219
|
+
const result = await mcpStatusTool(
|
|
220
|
+
mockMcpManager,
|
|
221
|
+
mockApprovalService,
|
|
222
|
+
mockFunctionBridge,
|
|
223
|
+
{}
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
expect(result.content[0].text).toContain('Total Requests: 10');
|
|
227
|
+
expect(result.content[0].text).toContain('Pending: 2');
|
|
228
|
+
expect(result.content[0].text).toContain('Approved: 5');
|
|
229
|
+
expect(result.content[0].text).toContain('Denied: 2');
|
|
230
|
+
expect(result.content[0].text).toContain('Expired: 1');
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('pending approvals section', () => {
|
|
235
|
+
it('should display pending approvals when they exist', async () => {
|
|
236
|
+
const now = Date.now();
|
|
237
|
+
mockApprovalService.getPendingApprovals.mockReturnValue([
|
|
238
|
+
{
|
|
239
|
+
id: 'approval-1',
|
|
240
|
+
duckName: 'TestDuck',
|
|
241
|
+
mcpServer: 'filesystem',
|
|
242
|
+
toolName: 'read_file',
|
|
243
|
+
arguments: {},
|
|
244
|
+
status: 'pending' as const,
|
|
245
|
+
timestamp: now - 30000,
|
|
246
|
+
expiresAt: now + 30000,
|
|
247
|
+
},
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
const result = await mcpStatusTool(
|
|
251
|
+
mockMcpManager,
|
|
252
|
+
mockApprovalService,
|
|
253
|
+
mockFunctionBridge,
|
|
254
|
+
{}
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
expect(result.content[0].text).toContain('Pending Approvals');
|
|
258
|
+
expect(result.content[0].text).toContain('TestDuck');
|
|
259
|
+
expect(result.content[0].text).toContain('filesystem:read_file');
|
|
260
|
+
expect(result.content[0].text).toMatch(/\d+s ago/);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should not display pending section when no pending approvals', async () => {
|
|
264
|
+
mockApprovalService.getStats.mockReturnValue({
|
|
265
|
+
...mockApprovalStats,
|
|
266
|
+
pending: 0,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const result = await mcpStatusTool(
|
|
270
|
+
mockMcpManager,
|
|
271
|
+
mockApprovalService,
|
|
272
|
+
mockFunctionBridge,
|
|
273
|
+
{}
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
expect(result.content[0].text).not.toContain('Pending Approvals:');
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('commands section', () => {
|
|
281
|
+
it('should display available commands', async () => {
|
|
282
|
+
const result = await mcpStatusTool(
|
|
283
|
+
mockMcpManager,
|
|
284
|
+
mockApprovalService,
|
|
285
|
+
mockFunctionBridge,
|
|
286
|
+
{}
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
expect(result.content[0].text).toContain('Commands');
|
|
290
|
+
expect(result.content[0].text).toContain('get_pending_approvals');
|
|
291
|
+
expect(result.content[0].text).toContain('approve_mcp_request');
|
|
292
|
+
expect(result.content[0].text).toContain('ask_duck');
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe('error handling', () => {
|
|
297
|
+
it('should handle exceptions gracefully', async () => {
|
|
298
|
+
mockMcpManager.getStatus.mockImplementation(() => {
|
|
299
|
+
throw new Error('Connection failed');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const result = await mcpStatusTool(
|
|
303
|
+
mockMcpManager,
|
|
304
|
+
mockApprovalService,
|
|
305
|
+
mockFunctionBridge,
|
|
306
|
+
{}
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
expect(result.isError).toBe(true);
|
|
310
|
+
expect(result.content[0].text).toContain('Failed to get MCP status');
|
|
311
|
+
expect(result.content[0].text).toContain('Connection failed');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should handle non-Error exceptions', async () => {
|
|
315
|
+
mockMcpManager.getStatus.mockImplementation(() => {
|
|
316
|
+
throw 'Unknown failure';
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const result = await mcpStatusTool(
|
|
320
|
+
mockMcpManager,
|
|
321
|
+
mockApprovalService,
|
|
322
|
+
mockFunctionBridge,
|
|
323
|
+
{}
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
expect(result.isError).toBe(true);
|
|
327
|
+
expect(result.content[0].text).toContain('Unknown failure');
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
});
|