mcp-rubber-duck 1.1.0

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.
Files changed (184) hide show
  1. package/.dockerignore +19 -0
  2. package/.env.desktop.example +145 -0
  3. package/.env.example +45 -0
  4. package/.env.pi.example +106 -0
  5. package/.env.template +165 -0
  6. package/.eslintrc.json +40 -0
  7. package/.github/ISSUE_TEMPLATE/bug_report.md +65 -0
  8. package/.github/ISSUE_TEMPLATE/feature_request.md +58 -0
  9. package/.github/ISSUE_TEMPLATE/question.md +67 -0
  10. package/.github/pull_request_template.md +111 -0
  11. package/.github/workflows/docker-build.yml +138 -0
  12. package/.github/workflows/release.yml +182 -0
  13. package/.github/workflows/security.yml +141 -0
  14. package/.github/workflows/semantic-release.yml +89 -0
  15. package/.prettierrc +10 -0
  16. package/.releaserc.json +66 -0
  17. package/CHANGELOG.md +95 -0
  18. package/CONTRIBUTING.md +242 -0
  19. package/Dockerfile +62 -0
  20. package/LICENSE +21 -0
  21. package/README.md +803 -0
  22. package/audit-ci.json +8 -0
  23. package/config/claude_desktop.json +14 -0
  24. package/config/config.example.json +91 -0
  25. package/dist/config/config.d.ts +51 -0
  26. package/dist/config/config.d.ts.map +1 -0
  27. package/dist/config/config.js +301 -0
  28. package/dist/config/config.js.map +1 -0
  29. package/dist/config/types.d.ts +356 -0
  30. package/dist/config/types.d.ts.map +1 -0
  31. package/dist/config/types.js +41 -0
  32. package/dist/config/types.js.map +1 -0
  33. package/dist/index.d.ts +3 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +109 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/providers/duck-provider-enhanced.d.ts +29 -0
  38. package/dist/providers/duck-provider-enhanced.d.ts.map +1 -0
  39. package/dist/providers/duck-provider-enhanced.js +230 -0
  40. package/dist/providers/duck-provider-enhanced.js.map +1 -0
  41. package/dist/providers/enhanced-manager.d.ts +54 -0
  42. package/dist/providers/enhanced-manager.d.ts.map +1 -0
  43. package/dist/providers/enhanced-manager.js +217 -0
  44. package/dist/providers/enhanced-manager.js.map +1 -0
  45. package/dist/providers/manager.d.ts +28 -0
  46. package/dist/providers/manager.d.ts.map +1 -0
  47. package/dist/providers/manager.js +204 -0
  48. package/dist/providers/manager.js.map +1 -0
  49. package/dist/providers/provider.d.ts +29 -0
  50. package/dist/providers/provider.d.ts.map +1 -0
  51. package/dist/providers/provider.js +179 -0
  52. package/dist/providers/provider.js.map +1 -0
  53. package/dist/providers/types.d.ts +69 -0
  54. package/dist/providers/types.d.ts.map +1 -0
  55. package/dist/providers/types.js +2 -0
  56. package/dist/providers/types.js.map +1 -0
  57. package/dist/server.d.ts +24 -0
  58. package/dist/server.d.ts.map +1 -0
  59. package/dist/server.js +501 -0
  60. package/dist/server.js.map +1 -0
  61. package/dist/services/approval.d.ts +44 -0
  62. package/dist/services/approval.d.ts.map +1 -0
  63. package/dist/services/approval.js +159 -0
  64. package/dist/services/approval.js.map +1 -0
  65. package/dist/services/cache.d.ts +21 -0
  66. package/dist/services/cache.d.ts.map +1 -0
  67. package/dist/services/cache.js +63 -0
  68. package/dist/services/cache.js.map +1 -0
  69. package/dist/services/conversation.d.ts +24 -0
  70. package/dist/services/conversation.d.ts.map +1 -0
  71. package/dist/services/conversation.js +108 -0
  72. package/dist/services/conversation.js.map +1 -0
  73. package/dist/services/function-bridge.d.ts +41 -0
  74. package/dist/services/function-bridge.d.ts.map +1 -0
  75. package/dist/services/function-bridge.js +259 -0
  76. package/dist/services/function-bridge.js.map +1 -0
  77. package/dist/services/health.d.ts +17 -0
  78. package/dist/services/health.d.ts.map +1 -0
  79. package/dist/services/health.js +77 -0
  80. package/dist/services/health.js.map +1 -0
  81. package/dist/services/mcp-client-manager.d.ts +49 -0
  82. package/dist/services/mcp-client-manager.d.ts.map +1 -0
  83. package/dist/services/mcp-client-manager.js +279 -0
  84. package/dist/services/mcp-client-manager.js.map +1 -0
  85. package/dist/tools/approve-mcp-request.d.ts +9 -0
  86. package/dist/tools/approve-mcp-request.d.ts.map +1 -0
  87. package/dist/tools/approve-mcp-request.js +111 -0
  88. package/dist/tools/approve-mcp-request.js.map +1 -0
  89. package/dist/tools/ask-duck.d.ts +9 -0
  90. package/dist/tools/ask-duck.d.ts.map +1 -0
  91. package/dist/tools/ask-duck.js +43 -0
  92. package/dist/tools/ask-duck.js.map +1 -0
  93. package/dist/tools/chat-duck.d.ts +9 -0
  94. package/dist/tools/chat-duck.d.ts.map +1 -0
  95. package/dist/tools/chat-duck.js +57 -0
  96. package/dist/tools/chat-duck.js.map +1 -0
  97. package/dist/tools/clear-conversations.d.ts +8 -0
  98. package/dist/tools/clear-conversations.d.ts.map +1 -0
  99. package/dist/tools/clear-conversations.js +17 -0
  100. package/dist/tools/clear-conversations.js.map +1 -0
  101. package/dist/tools/compare-ducks.d.ts +8 -0
  102. package/dist/tools/compare-ducks.d.ts.map +1 -0
  103. package/dist/tools/compare-ducks.js +49 -0
  104. package/dist/tools/compare-ducks.js.map +1 -0
  105. package/dist/tools/duck-council.d.ts +8 -0
  106. package/dist/tools/duck-council.d.ts.map +1 -0
  107. package/dist/tools/duck-council.js +69 -0
  108. package/dist/tools/duck-council.js.map +1 -0
  109. package/dist/tools/get-pending-approvals.d.ts +15 -0
  110. package/dist/tools/get-pending-approvals.d.ts.map +1 -0
  111. package/dist/tools/get-pending-approvals.js +74 -0
  112. package/dist/tools/get-pending-approvals.js.map +1 -0
  113. package/dist/tools/list-ducks.d.ts +9 -0
  114. package/dist/tools/list-ducks.d.ts.map +1 -0
  115. package/dist/tools/list-ducks.js +47 -0
  116. package/dist/tools/list-ducks.js.map +1 -0
  117. package/dist/tools/list-models.d.ts +8 -0
  118. package/dist/tools/list-models.d.ts.map +1 -0
  119. package/dist/tools/list-models.js +72 -0
  120. package/dist/tools/list-models.js.map +1 -0
  121. package/dist/tools/mcp-status.d.ts +17 -0
  122. package/dist/tools/mcp-status.d.ts.map +1 -0
  123. package/dist/tools/mcp-status.js +100 -0
  124. package/dist/tools/mcp-status.js.map +1 -0
  125. package/dist/utils/ascii-art.d.ts +19 -0
  126. package/dist/utils/ascii-art.d.ts.map +1 -0
  127. package/dist/utils/ascii-art.js +73 -0
  128. package/dist/utils/ascii-art.js.map +1 -0
  129. package/dist/utils/logger.d.ts +3 -0
  130. package/dist/utils/logger.d.ts.map +1 -0
  131. package/dist/utils/logger.js +86 -0
  132. package/dist/utils/logger.js.map +1 -0
  133. package/dist/utils/safe-logger.d.ts +23 -0
  134. package/dist/utils/safe-logger.d.ts.map +1 -0
  135. package/dist/utils/safe-logger.js +145 -0
  136. package/dist/utils/safe-logger.js.map +1 -0
  137. package/docker-compose.yml +161 -0
  138. package/jest.config.js +26 -0
  139. package/package.json +65 -0
  140. package/scripts/build-multiarch.sh +290 -0
  141. package/scripts/deploy-raspbian.sh +410 -0
  142. package/scripts/deploy.sh +322 -0
  143. package/scripts/gh-deploy.sh +343 -0
  144. package/scripts/setup-docker-raspbian.sh +530 -0
  145. package/server.json +8 -0
  146. package/src/config/config.ts +357 -0
  147. package/src/config/types.ts +89 -0
  148. package/src/index.ts +114 -0
  149. package/src/providers/duck-provider-enhanced.ts +294 -0
  150. package/src/providers/enhanced-manager.ts +290 -0
  151. package/src/providers/manager.ts +257 -0
  152. package/src/providers/provider.ts +207 -0
  153. package/src/providers/types.ts +78 -0
  154. package/src/server.ts +603 -0
  155. package/src/services/approval.ts +225 -0
  156. package/src/services/cache.ts +79 -0
  157. package/src/services/conversation.ts +146 -0
  158. package/src/services/function-bridge.ts +329 -0
  159. package/src/services/health.ts +107 -0
  160. package/src/services/mcp-client-manager.ts +362 -0
  161. package/src/tools/approve-mcp-request.ts +126 -0
  162. package/src/tools/ask-duck.ts +74 -0
  163. package/src/tools/chat-duck.ts +82 -0
  164. package/src/tools/clear-conversations.ts +24 -0
  165. package/src/tools/compare-ducks.ts +67 -0
  166. package/src/tools/duck-council.ts +88 -0
  167. package/src/tools/get-pending-approvals.ts +90 -0
  168. package/src/tools/list-ducks.ts +65 -0
  169. package/src/tools/list-models.ts +101 -0
  170. package/src/tools/mcp-status.ts +117 -0
  171. package/src/utils/ascii-art.ts +85 -0
  172. package/src/utils/logger.ts +116 -0
  173. package/src/utils/safe-logger.ts +165 -0
  174. package/systemd/mcp-rubber-duck-with-ollama.service +55 -0
  175. package/systemd/mcp-rubber-duck.service +58 -0
  176. package/test-functionality.js +147 -0
  177. package/test-mcp-interface.js +221 -0
  178. package/tests/ascii-art.test.ts +36 -0
  179. package/tests/config.test.ts +239 -0
  180. package/tests/conversation.test.ts +308 -0
  181. package/tests/mcp-bridge.test.ts +291 -0
  182. package/tests/providers.test.ts +269 -0
  183. package/tests/tools/clear-conversations.test.ts +163 -0
  184. package/tsconfig.json +26 -0
@@ -0,0 +1,269 @@
1
+ import { describe, it, expect, jest, beforeEach } from '@jest/globals';
2
+
3
+ // Mock OpenAI BEFORE importing the provider
4
+ const mockCreate = jest.fn();
5
+ jest.mock('openai', () => {
6
+ const MockOpenAI = jest.fn().mockImplementation(() => ({
7
+ chat: {
8
+ completions: {
9
+ create: mockCreate,
10
+ },
11
+ },
12
+ }));
13
+ return {
14
+ __esModule: true,
15
+ default: MockOpenAI,
16
+ };
17
+ });
18
+
19
+ // Mock config manager and logger
20
+ jest.mock('../src/config/config');
21
+ jest.mock('../src/utils/logger');
22
+
23
+ // NOW import the modules after setting up mocks
24
+ import { DuckProvider } from '../src/providers/provider';
25
+ import { ProviderManager } from '../src/providers/manager';
26
+ import { ConfigManager } from '../src/config/config';
27
+
28
+ describe('DuckProvider', () => {
29
+ let provider: DuckProvider;
30
+
31
+ beforeEach(() => {
32
+ jest.clearAllMocks();
33
+
34
+ // Setup mock response
35
+ mockCreate.mockResolvedValue({
36
+ choices: [{
37
+ message: { content: 'Mocked response' },
38
+ finish_reason: 'stop',
39
+ }],
40
+ usage: {
41
+ prompt_tokens: 10,
42
+ completion_tokens: 20,
43
+ total_tokens: 30,
44
+ },
45
+ model: 'mock-model',
46
+ });
47
+
48
+ provider = new DuckProvider('test', 'Test Duck', {
49
+ apiKey: 'test-key',
50
+ baseURL: 'https://api.test.com/v1',
51
+ model: 'test-model',
52
+ temperature: 0.7,
53
+ });
54
+
55
+ // Override the method on the actual instance since Jest ESM mocking isn't working
56
+ provider['client'].chat.completions.create = mockCreate;
57
+ });
58
+
59
+ it('should create a provider instance', () => {
60
+ expect(provider).toBeDefined();
61
+ expect(provider.name).toBe('test');
62
+ expect(provider.nickname).toBe('Test Duck');
63
+ });
64
+
65
+ it('should get provider info', () => {
66
+ const info = provider.getInfo();
67
+ expect(info).toEqual({
68
+ name: 'test',
69
+ nickname: 'Test Duck',
70
+ model: 'test-model',
71
+ baseURL: 'https://api.test.com/v1',
72
+ hasApiKey: true,
73
+ });
74
+ });
75
+
76
+ it('should send chat request', async () => {
77
+ console.log('Starting chat request test');
78
+ console.log('Mock create has been called:', mockCreate.mock.calls.length, 'times');
79
+
80
+ const response = await provider.chat({
81
+ messages: [
82
+ { role: 'user', content: 'Hello', timestamp: new Date() },
83
+ ],
84
+ });
85
+
86
+ console.log('Chat response received:', response);
87
+ expect(response).toBeDefined();
88
+ expect(response.content).toBe('Mocked response');
89
+ expect(response.usage).toBeDefined();
90
+ expect(response.model).toBe('test-model');
91
+ });
92
+
93
+ it('should use correct parameters for o1 models', async () => {
94
+ mockCreate.mockClear();
95
+
96
+ const testProvider = new DuckProvider('test', 'Test Duck', {
97
+ apiKey: 'test-key',
98
+ baseURL: 'https://api.test.com/v1',
99
+ model: 'o1',
100
+ });
101
+
102
+ // Override the method on the actual instance since Jest ESM mocking isn't working
103
+ testProvider['client'].chat.completions.create = mockCreate;
104
+
105
+ await testProvider.chat({
106
+ messages: [{ role: 'user', content: 'Hello', timestamp: new Date() }],
107
+ model: 'o1',
108
+ });
109
+
110
+ expect(mockCreate).toHaveBeenCalledTimes(1);
111
+ const calls = (mockCreate as any).mock.calls;
112
+ expect(calls.length).toBeGreaterThan(0);
113
+ const callParams = calls[0][0];
114
+
115
+ // o1 models should NOT have temperature or token limits
116
+ expect(callParams).not.toHaveProperty('temperature');
117
+ });
118
+
119
+ it('should use correct parameters for GPT-5 models', async () => {
120
+ mockCreate.mockClear();
121
+
122
+ const testProvider = new DuckProvider('test', 'Test Duck', {
123
+ apiKey: 'test-key',
124
+ baseURL: 'https://api.test.com/v1',
125
+ model: 'gpt-5',
126
+ });
127
+
128
+ // Override the method on the actual instance since Jest ESM mocking isn't working
129
+ testProvider['client'].chat.completions.create = mockCreate;
130
+
131
+ await testProvider.chat({
132
+ messages: [{ role: 'user', content: 'Hello', timestamp: new Date() }],
133
+ model: 'gpt-5',
134
+ });
135
+
136
+ expect(mockCreate).toHaveBeenCalledTimes(1);
137
+ const calls = (mockCreate as any).mock.calls;
138
+ expect(calls.length).toBeGreaterThan(0);
139
+ const callParams = calls[0][0];
140
+
141
+ // GPT-5 models should NOT have temperature or token limits
142
+ expect(callParams).not.toHaveProperty('temperature');
143
+ });
144
+
145
+ it('should use correct parameters for non-o1 models', async () => {
146
+ mockCreate.mockClear();
147
+
148
+ const testProvider = new DuckProvider('test', 'Test Duck', {
149
+ apiKey: 'test-key',
150
+ baseURL: 'https://api.test.com/v1',
151
+ model: 'gpt-4',
152
+ });
153
+
154
+ // Override the method on the actual instance since Jest ESM mocking isn't working
155
+ testProvider['client'].chat.completions.create = mockCreate;
156
+
157
+ await testProvider.chat({
158
+ messages: [{ role: 'user', content: 'Hello', timestamp: new Date() }],
159
+ model: 'gpt-4',
160
+ });
161
+
162
+ expect(mockCreate).toHaveBeenCalledTimes(1);
163
+ const calls = (mockCreate as any).mock.calls;
164
+ expect(calls.length).toBeGreaterThan(0);
165
+ const callParams = calls[0][0];
166
+
167
+ // non-o1 models should have temperature but no token limits
168
+ expect(callParams).toHaveProperty('temperature');
169
+ });
170
+ });
171
+
172
+ describe('ProviderManager', () => {
173
+ let manager: ProviderManager;
174
+ let mockConfigManager: jest.Mocked<ConfigManager>;
175
+
176
+ beforeEach(() => {
177
+ jest.clearAllMocks();
178
+
179
+ // Setup mock response
180
+ mockCreate.mockResolvedValue({
181
+ choices: [{
182
+ message: { content: 'Mocked response' },
183
+ finish_reason: 'stop',
184
+ }],
185
+ usage: {
186
+ prompt_tokens: 10,
187
+ completion_tokens: 20,
188
+ total_tokens: 30,
189
+ },
190
+ model: 'mock-model',
191
+ });
192
+
193
+ mockConfigManager = {
194
+ getConfig: jest.fn().mockReturnValue({
195
+ providers: {
196
+ test1: {
197
+ api_key: 'key1',
198
+ base_url: 'https://api1.test.com/v1',
199
+ default_model: 'model1',
200
+ nickname: 'Duck 1',
201
+ models: ['model1'],
202
+ },
203
+ test2: {
204
+ api_key: 'key2',
205
+ base_url: 'https://api2.test.com/v1',
206
+ default_model: 'model2',
207
+ nickname: 'Duck 2',
208
+ models: ['model2'],
209
+ },
210
+ },
211
+ default_provider: 'test1',
212
+ cache_ttl: 300,
213
+ enable_failover: true,
214
+ default_temperature: 0.7,
215
+ }),
216
+ } as any;
217
+
218
+ manager = new ProviderManager(mockConfigManager);
219
+
220
+ // Override the client method on all providers in the manager
221
+ const provider1 = manager.getProvider('test1');
222
+ const provider2 = manager.getProvider('test2');
223
+ provider1['client'].chat.completions.create = mockCreate;
224
+ provider2['client'].chat.completions.create = mockCreate;
225
+ });
226
+
227
+ it('should initialize providers from config', () => {
228
+ const providers = manager.getProviderNames();
229
+ expect(providers).toContain('test1');
230
+ expect(providers).toContain('test2');
231
+ });
232
+
233
+ it('should get a specific provider', () => {
234
+ const provider = manager.getProvider('test1');
235
+ expect(provider).toBeDefined();
236
+ expect(provider.name).toBe('test1');
237
+ });
238
+
239
+ it('should get default provider when no name specified', () => {
240
+ const provider = manager.getProvider();
241
+ expect(provider).toBeDefined();
242
+ expect(provider.name).toBe('test1');
243
+ });
244
+
245
+ it('should throw error for non-existent provider', () => {
246
+ expect(() => manager.getProvider('nonexistent')).toThrow(
247
+ 'Duck "nonexistent" not found in the pond'
248
+ );
249
+ });
250
+
251
+ it('should ask a duck', async () => {
252
+ const response = await manager.askDuck('test1', 'Hello');
253
+ expect(response).toBeDefined();
254
+ expect(response.provider).toBe('test1');
255
+ expect(response.content).toBe('Mocked response');
256
+ });
257
+
258
+ it('should compare multiple ducks', async () => {
259
+ const responses = await manager.compareDucks('Hello', ['test1', 'test2']);
260
+ expect(responses).toHaveLength(2);
261
+ expect(responses[0].provider).toBe('test1');
262
+ expect(responses[1].provider).toBe('test2');
263
+ });
264
+
265
+ it('should run duck council', async () => {
266
+ const responses = await manager.duckCouncil('Hello');
267
+ expect(responses).toHaveLength(2);
268
+ });
269
+ });
@@ -0,0 +1,163 @@
1
+ import { describe, it, expect, jest, beforeEach } from '@jest/globals';
2
+ import { clearConversationsTool } from '../../src/tools/clear-conversations.js';
3
+ import { ConversationManager } from '../../src/services/conversation.js';
4
+
5
+ // Mock logger to avoid console noise during tests
6
+ jest.mock('../../src/utils/logger');
7
+
8
+ describe('clear_conversations tool', () => {
9
+ let conversationManager: ConversationManager;
10
+
11
+ beforeEach(() => {
12
+ conversationManager = new ConversationManager();
13
+ });
14
+
15
+ describe('tool execution', () => {
16
+ it('should call ConversationManager.clearAll and return proper response format', () => {
17
+ // Create some conversations first
18
+ conversationManager.createConversation('test-1', 'openai');
19
+ conversationManager.addMessage('test-1', {
20
+ role: 'user' as const,
21
+ content: 'Hello',
22
+ timestamp: new Date(),
23
+ });
24
+
25
+ const result = clearConversationsTool(conversationManager, {});
26
+
27
+ expect(result).toHaveProperty('content');
28
+ expect(result.content).toHaveLength(1);
29
+ expect(result.content[0]).toHaveProperty('type', 'text');
30
+ expect(result.content[0].text).toContain('🧹 Cleared 1 conversation (1 message)');
31
+ expect(result.content[0].text).toContain('🦆 All ducks now have a fresh start!');
32
+
33
+ // Verify conversations were actually cleared
34
+ expect(conversationManager.getConversation('test-1')).toBeUndefined();
35
+ });
36
+
37
+ it('should handle empty state gracefully', () => {
38
+ const result = clearConversationsTool(conversationManager, {});
39
+
40
+ expect(result.content[0].text).toContain('🧹 No conversations to clear - memory is already empty!');
41
+ expect(result.content[0].text).toContain('🦆 All ducks now have a fresh start!');
42
+ });
43
+
44
+ it('should handle multiple conversations correctly', () => {
45
+ // Create multiple conversations with different message counts
46
+ conversationManager.createConversation('test-1', 'openai');
47
+ conversationManager.addMessage('test-1', {
48
+ role: 'user' as const,
49
+ content: 'Hello 1',
50
+ timestamp: new Date(),
51
+ });
52
+ conversationManager.addMessage('test-1', {
53
+ role: 'assistant' as const,
54
+ content: 'Hi 1',
55
+ timestamp: new Date(),
56
+ });
57
+
58
+ conversationManager.createConversation('test-2', 'groq');
59
+ conversationManager.addMessage('test-2', {
60
+ role: 'user' as const,
61
+ content: 'Hello 2',
62
+ timestamp: new Date(),
63
+ });
64
+
65
+ conversationManager.createConversation('test-3', 'gemini');
66
+ // No messages in test-3
67
+
68
+ const result = clearConversationsTool(conversationManager, {});
69
+
70
+ expect(result.content[0].text).toContain('🧹 Cleared 3 conversations (3 messages)');
71
+ expect(result.content[0].text).toContain('🦆 All ducks now have a fresh start!');
72
+
73
+ // Verify all conversations were cleared
74
+ expect(conversationManager.listConversations()).toHaveLength(0);
75
+ });
76
+
77
+ it('should handle singular vs plural correctly', () => {
78
+ // Test single conversation with single message
79
+ conversationManager.createConversation('test-1', 'openai');
80
+ conversationManager.addMessage('test-1', {
81
+ role: 'user' as const,
82
+ content: 'Hello',
83
+ timestamp: new Date(),
84
+ });
85
+
86
+ const result = clearConversationsTool(conversationManager, {});
87
+
88
+ // Should use singular form
89
+ expect(result.content[0].text).toContain('🧹 Cleared 1 conversation (1 message)');
90
+ expect(result.content[0].text).not.toContain('conversations');
91
+ expect(result.content[0].text).not.toContain('messages)');
92
+ });
93
+
94
+ it('should handle args parameter (even though unused)', () => {
95
+ const args = { unused: 'parameter' };
96
+
97
+ conversationManager.createConversation('test-1', 'openai');
98
+ const result = clearConversationsTool(conversationManager, args);
99
+
100
+ expect(result).toHaveProperty('content');
101
+ expect(result.content[0].text).toContain('🧹 Cleared 1 conversation');
102
+ });
103
+
104
+ it('should return consistent response structure', () => {
105
+ const result = clearConversationsTool(conversationManager, {});
106
+
107
+ // Verify response structure matches MCP tool format
108
+ expect(result).toEqual({
109
+ content: [
110
+ {
111
+ type: 'text',
112
+ text: expect.stringContaining('🧹'),
113
+ },
114
+ ],
115
+ });
116
+
117
+ expect(result.content[0].text).toContain('🦆 All ducks now have a fresh start!');
118
+ });
119
+ });
120
+
121
+ describe('integration with ConversationManager', () => {
122
+ it('should properly clear all conversation state', () => {
123
+ // Create complex scenario
124
+ conversationManager.createConversation('debug-session', 'openai');
125
+ conversationManager.addMessage('debug-session', {
126
+ role: 'user' as const,
127
+ content: 'Help with bug',
128
+ timestamp: new Date(),
129
+ });
130
+ conversationManager.addMessage('debug-session', {
131
+ role: 'assistant' as const,
132
+ content: 'Sure, what\'s the issue?',
133
+ timestamp: new Date(),
134
+ });
135
+
136
+ conversationManager.createConversation('code-review', 'groq');
137
+ conversationManager.addMessage('code-review', {
138
+ role: 'user' as const,
139
+ content: 'Review this code',
140
+ timestamp: new Date(),
141
+ });
142
+
143
+ // Switch provider in second conversation
144
+ conversationManager.switchProvider('code-review', 'gemini');
145
+
146
+ // Verify setup
147
+ expect(conversationManager.listConversations()).toHaveLength(2);
148
+ expect(conversationManager.getConversation('debug-session')!.messages).toHaveLength(2);
149
+ expect(conversationManager.getConversation('code-review')!.messages).toHaveLength(2); // 1 + 1 system message
150
+
151
+ // Clear all
152
+ const result = clearConversationsTool(conversationManager, {});
153
+
154
+ // Verify complete cleanup
155
+ expect(conversationManager.listConversations()).toHaveLength(0);
156
+ expect(conversationManager.getConversation('debug-session')).toBeUndefined();
157
+ expect(conversationManager.getConversation('code-review')).toBeUndefined();
158
+
159
+ // Verify counts in response
160
+ expect(result.content[0].text).toContain('🧹 Cleared 2 conversations (4 messages)');
161
+ });
162
+ });
163
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "noImplicitReturns": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "allowSyntheticDefaultImports": true,
22
+ "isolatedModules": true
23
+ },
24
+ "include": ["src/**/*"],
25
+ "exclude": ["node_modules", "dist", "tests"]
26
+ }