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
package/src/server.ts ADDED
@@ -0,0 +1,603 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import {
4
+ CallToolRequestSchema,
5
+ ListToolsRequestSchema,
6
+ Tool,
7
+ } from '@modelcontextprotocol/sdk/types.js';
8
+
9
+ import { ConfigManager } from './config/config.js';
10
+ import { ProviderManager } from './providers/manager.js';
11
+ import { EnhancedProviderManager } from './providers/enhanced-manager.js';
12
+ import { ConversationManager } from './services/conversation.js';
13
+ import { ResponseCache } from './services/cache.js';
14
+ import { HealthMonitor } from './services/health.js';
15
+ import { MCPClientManager } from './services/mcp-client-manager.js';
16
+ import { DuckResponse } from './config/types.js';
17
+ import { ApprovalService } from './services/approval.js';
18
+ import { FunctionBridge } from './services/function-bridge.js';
19
+ import { logger } from './utils/logger.js';
20
+ import { duckArt, getRandomDuckMessage } from './utils/ascii-art.js';
21
+
22
+ // Import tools
23
+ import { askDuckTool } from './tools/ask-duck.js';
24
+ import { chatDuckTool } from './tools/chat-duck.js';
25
+ import { clearConversationsTool } from './tools/clear-conversations.js';
26
+ import { listDucksTool } from './tools/list-ducks.js';
27
+ import { listModelsTool } from './tools/list-models.js';
28
+ import { compareDucksTool } from './tools/compare-ducks.js';
29
+ import { duckCouncilTool } from './tools/duck-council.js';
30
+
31
+ // Import MCP tools
32
+ import { getPendingApprovalsTool } from './tools/get-pending-approvals.js';
33
+ import { approveMCPRequestTool } from './tools/approve-mcp-request.js';
34
+ import { mcpStatusTool } from './tools/mcp-status.js';
35
+
36
+ export class RubberDuckServer {
37
+ private server: Server;
38
+ private configManager: ConfigManager;
39
+ private providerManager: ProviderManager;
40
+ private enhancedProviderManager?: EnhancedProviderManager;
41
+ private conversationManager: ConversationManager;
42
+ private cache: ResponseCache;
43
+ private healthMonitor: HealthMonitor;
44
+
45
+ // MCP Bridge components
46
+ private mcpClientManager?: MCPClientManager;
47
+ private approvalService?: ApprovalService;
48
+ private functionBridge?: FunctionBridge;
49
+ private mcpEnabled: boolean = false;
50
+
51
+ constructor() {
52
+ this.server = new Server(
53
+ {
54
+ name: 'mcp-rubber-duck',
55
+ version: '1.0.0',
56
+ },
57
+ {
58
+ capabilities: {
59
+ tools: {},
60
+ },
61
+ }
62
+ );
63
+
64
+ // Initialize managers
65
+ this.configManager = new ConfigManager();
66
+ this.providerManager = new ProviderManager(this.configManager);
67
+ this.conversationManager = new ConversationManager();
68
+ this.cache = new ResponseCache(this.configManager.getConfig().cache_ttl);
69
+ this.healthMonitor = new HealthMonitor(this.providerManager);
70
+
71
+ // Initialize MCP bridge if enabled
72
+ this.initializeMCPBridge();
73
+
74
+ this.setupHandlers();
75
+ }
76
+
77
+ private initializeMCPBridge(): void {
78
+ const config = this.configManager.getConfig();
79
+ const mcpConfig = config.mcp_bridge;
80
+
81
+ if (!mcpConfig?.enabled) {
82
+ logger.info('MCP bridge disabled in configuration');
83
+ return;
84
+ }
85
+
86
+ try {
87
+ logger.info('Initializing MCP bridge...');
88
+
89
+ // Initialize MCP client manager
90
+ this.mcpClientManager = new MCPClientManager(mcpConfig.mcp_servers);
91
+
92
+ // Initialize approval service
93
+ this.approvalService = new ApprovalService(mcpConfig.approval_timeout);
94
+
95
+ // Initialize function bridge
96
+ this.functionBridge = new FunctionBridge(
97
+ this.mcpClientManager,
98
+ this.approvalService,
99
+ mcpConfig.trusted_tools,
100
+ mcpConfig.approval_mode,
101
+ mcpConfig.trusted_tools_by_server || {}
102
+ );
103
+
104
+ // Initialize enhanced provider manager
105
+ this.enhancedProviderManager = new EnhancedProviderManager(
106
+ this.configManager,
107
+ this.functionBridge
108
+ );
109
+
110
+ this.mcpEnabled = true;
111
+ logger.info('MCP bridge initialized successfully');
112
+ } catch (error: unknown) {
113
+ const errorMessage = error instanceof Error ? error.message : String(error);
114
+ logger.error('Failed to initialize MCP bridge:', errorMessage);
115
+ logger.warn('Falling back to regular duck provider functionality');
116
+ this.mcpEnabled = false;
117
+ }
118
+ }
119
+
120
+ private setupHandlers() {
121
+ // List available tools
122
+ this.server.setRequestHandler(ListToolsRequestSchema, () => {
123
+ return { tools: this.getTools() };
124
+ });
125
+
126
+ // Handle tool calls
127
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
128
+ const { name, arguments: args } = request.params;
129
+
130
+ try {
131
+ switch (name) {
132
+ case 'ask_duck':
133
+ // Use enhanced provider manager if MCP is enabled
134
+ if (this.mcpEnabled && this.enhancedProviderManager) {
135
+ return await this.handleAskDuckWithMCP(args || {});
136
+ }
137
+ return await askDuckTool(this.providerManager, this.cache, args || {});
138
+
139
+ case 'chat_with_duck':
140
+ return await chatDuckTool(this.providerManager, this.conversationManager, args || {});
141
+
142
+ case 'clear_conversations':
143
+ return clearConversationsTool(this.conversationManager, args || {});
144
+
145
+ case 'list_ducks':
146
+ return await listDucksTool(this.providerManager, this.healthMonitor, args || {});
147
+
148
+ case 'list_models':
149
+ return await listModelsTool(this.providerManager, args || {});
150
+
151
+ case 'compare_ducks':
152
+ // Use enhanced provider manager if MCP is enabled
153
+ if (this.mcpEnabled && this.enhancedProviderManager) {
154
+ return await this.handleCompareDucksWithMCP(args || {});
155
+ }
156
+ return await compareDucksTool(this.providerManager, this.cache, args || {});
157
+
158
+ case 'duck_council':
159
+ // Use enhanced provider manager if MCP is enabled
160
+ if (this.mcpEnabled && this.enhancedProviderManager) {
161
+ return await this.handleDuckCouncilWithMCP(args || {});
162
+ }
163
+ return await duckCouncilTool(this.providerManager, args || {});
164
+
165
+ // MCP-specific tools
166
+ case 'get_pending_approvals':
167
+ if (!this.approvalService) {
168
+ throw new Error('MCP bridge not enabled');
169
+ }
170
+ return getPendingApprovalsTool(this.approvalService, args || {});
171
+
172
+ case 'approve_mcp_request':
173
+ if (!this.approvalService) {
174
+ throw new Error('MCP bridge not enabled');
175
+ }
176
+ return approveMCPRequestTool(this.approvalService, args || {});
177
+
178
+ case 'mcp_status':
179
+ if (!this.mcpClientManager || !this.approvalService || !this.functionBridge) {
180
+ throw new Error('MCP bridge not enabled');
181
+ }
182
+ return await mcpStatusTool(
183
+ this.mcpClientManager,
184
+ this.approvalService,
185
+ this.functionBridge,
186
+ args || {}
187
+ );
188
+
189
+ default:
190
+ throw new Error(`Unknown tool: ${name}`);
191
+ }
192
+ } catch (error: unknown) {
193
+ logger.error(`Tool execution error for ${name}:`, error);
194
+ const errorMessage = error instanceof Error ? error.message : String(error);
195
+ return {
196
+ content: [
197
+ {
198
+ type: 'text',
199
+ text: `${getRandomDuckMessage('error')}\n\nError: ${errorMessage}`,
200
+ },
201
+ ],
202
+ isError: true,
203
+ };
204
+ }
205
+ });
206
+
207
+ // Handle errors
208
+ this.server.onerror = (error) => {
209
+ logger.error('Server error:', error);
210
+ };
211
+ }
212
+
213
+ // MCP-enhanced tool handlers
214
+ private async handleAskDuckWithMCP(args: Record<string, unknown>) {
215
+ if (!this.enhancedProviderManager || !this.cache) {
216
+ throw new Error('Enhanced provider manager not available');
217
+ }
218
+
219
+ const { prompt, provider, model, temperature } = args as {
220
+ prompt?: string;
221
+ provider?: string;
222
+ model?: string;
223
+ temperature?: number;
224
+ };
225
+
226
+ if (!prompt) {
227
+ throw new Error('Prompt is required');
228
+ }
229
+
230
+ // Generate cache key (same as regular ask_duck)
231
+ const cacheKey = this.cache.generateKey(provider || 'default', prompt, { model, temperature });
232
+
233
+ // Try to get cached response
234
+ const { value: response, cached } = await this.cache.getOrSet(cacheKey, async () => {
235
+ return await this.enhancedProviderManager!.askDuckWithMCP(provider, prompt, {
236
+ model,
237
+ temperature,
238
+ });
239
+ });
240
+
241
+ // Format the response with MCP information
242
+ const formattedResponse = this.formatEnhancedDuckResponse(response, cached);
243
+
244
+ return {
245
+ content: [
246
+ {
247
+ type: 'text',
248
+ text: formattedResponse,
249
+ },
250
+ ],
251
+ };
252
+ }
253
+
254
+ private async handleCompareDucksWithMCP(args: Record<string, unknown>) {
255
+ if (!this.enhancedProviderManager) {
256
+ throw new Error('Enhanced provider manager not available');
257
+ }
258
+
259
+ const { prompt, providers, model } = args as {
260
+ prompt: string;
261
+ providers?: string[];
262
+ model?: string;
263
+ };
264
+
265
+ const responses = await this.enhancedProviderManager.compareDucksWithMCP(prompt, providers, {
266
+ model,
267
+ });
268
+
269
+ const formattedResponse = responses
270
+ .map((response) => this.formatEnhancedDuckResponse(response))
271
+ .join('\n\n═══════════════════════════════════════\n\n');
272
+
273
+ return {
274
+ content: [
275
+ {
276
+ type: 'text',
277
+ text: formattedResponse,
278
+ },
279
+ ],
280
+ };
281
+ }
282
+
283
+ private async handleDuckCouncilWithMCP(args: Record<string, unknown>) {
284
+ if (!this.enhancedProviderManager) {
285
+ throw new Error('Enhanced provider manager not available');
286
+ }
287
+
288
+ const { prompt, model } = args as { prompt: string; model?: string };
289
+
290
+ const responses = await this.enhancedProviderManager.duckCouncilWithMCP(prompt, { model });
291
+
292
+ const header = '🦆 Duck Council in Session 🦆\n=============================';
293
+ const formattedResponse = responses
294
+ .map((response) => this.formatEnhancedDuckResponse(response))
295
+ .join('\n\n═══════════════════════════════════════\n\n');
296
+
297
+ return {
298
+ content: [
299
+ {
300
+ type: 'text',
301
+ text: `${header}\n\n${formattedResponse}`,
302
+ },
303
+ ],
304
+ };
305
+ }
306
+
307
+ private formatEnhancedDuckResponse(
308
+ response: DuckResponse & {
309
+ pendingApprovals?: { id: string; message: string }[];
310
+ mcpResults?: unknown[];
311
+ },
312
+ cached?: boolean
313
+ ): string {
314
+ let formatted = `🦆 **${response.nickname}** (${response.provider})\n─────────────────────────────────────\n${response.content}`;
315
+
316
+ // Add pending approvals if any
317
+ if (response.pendingApprovals && response.pendingApprovals.length > 0) {
318
+ formatted += '\n\n⏳ **Pending Approvals:**';
319
+ response.pendingApprovals.forEach((approval) => {
320
+ formatted += `\n- ${approval.message} (ID: ${approval.id})`;
321
+ });
322
+ formatted +=
323
+ '\n\n💡 Use `get_pending_approvals` and `approve_mcp_request` to manage approvals';
324
+ }
325
+
326
+ // Add MCP results indicator
327
+ if (response.mcpResults && response.mcpResults.length > 0) {
328
+ formatted += '\n\n🔧 **Used MCP Tools:** ' + response.mcpResults.length + ' tool call(s)';
329
+ }
330
+
331
+ // Add model and performance info
332
+ formatted += `\n\n📍 Model: ${response.model}`;
333
+ if (response.usage) {
334
+ formatted += ` | 📊 Tokens: ${response.usage.total_tokens}`;
335
+ }
336
+ formatted += ` | ⏱️ ${response.latency}ms`;
337
+ if (cached) {
338
+ formatted += ' | 💾 Cached';
339
+ } else if (response.mcpResults) {
340
+ formatted += ' | 🔄 MCP Enhanced';
341
+ } else {
342
+ formatted += ' | 🔄 Fresh';
343
+ }
344
+
345
+ return formatted;
346
+ }
347
+
348
+ private getTools(): Tool[] {
349
+ const baseTools: Tool[] = [
350
+ {
351
+ name: 'ask_duck',
352
+ description: this.mcpEnabled
353
+ ? 'Ask a question to a specific LLM provider (duck) with MCP tool access'
354
+ : 'Ask a question to a specific LLM provider (duck)',
355
+ inputSchema: {
356
+ type: 'object',
357
+ properties: {
358
+ prompt: {
359
+ type: 'string',
360
+ description: 'The question or prompt to send to the duck',
361
+ },
362
+ provider: {
363
+ type: 'string',
364
+ description: 'The provider name (optional, uses default if not specified)',
365
+ },
366
+ model: {
367
+ type: 'string',
368
+ description:
369
+ 'Specific model to use (optional, uses provider default if not specified)',
370
+ },
371
+ temperature: {
372
+ type: 'number',
373
+ description: 'Temperature for response generation (0-2)',
374
+ minimum: 0,
375
+ maximum: 2,
376
+ },
377
+ },
378
+ required: ['prompt'],
379
+ },
380
+ },
381
+ {
382
+ name: 'chat_with_duck',
383
+ description: 'Have a conversation with a duck, maintaining context across messages',
384
+ inputSchema: {
385
+ type: 'object',
386
+ properties: {
387
+ conversation_id: {
388
+ type: 'string',
389
+ description: 'Conversation ID (creates new if not exists)',
390
+ },
391
+ message: {
392
+ type: 'string',
393
+ description: 'Your message to the duck',
394
+ },
395
+ provider: {
396
+ type: 'string',
397
+ description: 'Provider to use (can switch mid-conversation)',
398
+ },
399
+ model: {
400
+ type: 'string',
401
+ description: 'Specific model to use (optional)',
402
+ },
403
+ },
404
+ required: ['conversation_id', 'message'],
405
+ },
406
+ },
407
+ {
408
+ name: 'clear_conversations',
409
+ description: 'Clear all conversation history and start fresh',
410
+ inputSchema: {
411
+ type: 'object',
412
+ properties: {},
413
+ },
414
+ },
415
+ {
416
+ name: 'list_ducks',
417
+ description: 'List all available LLM providers (ducks) and their status',
418
+ inputSchema: {
419
+ type: 'object',
420
+ properties: {
421
+ check_health: {
422
+ type: 'boolean',
423
+ description: 'Perform health check on all providers',
424
+ default: false,
425
+ },
426
+ },
427
+ },
428
+ },
429
+ {
430
+ name: 'list_models',
431
+ description: 'List available models for LLM providers',
432
+ inputSchema: {
433
+ type: 'object',
434
+ properties: {
435
+ provider: {
436
+ type: 'string',
437
+ description: 'Provider name (optional, lists all if not specified)',
438
+ },
439
+ fetch_latest: {
440
+ type: 'boolean',
441
+ description: 'Fetch latest models from API vs using cached/configured',
442
+ default: false,
443
+ },
444
+ },
445
+ },
446
+ },
447
+ {
448
+ name: 'compare_ducks',
449
+ description: 'Ask the same question to multiple ducks simultaneously',
450
+ inputSchema: {
451
+ type: 'object',
452
+ properties: {
453
+ prompt: {
454
+ type: 'string',
455
+ description: 'The question to ask all ducks',
456
+ },
457
+ providers: {
458
+ type: 'array',
459
+ items: {
460
+ type: 'string',
461
+ },
462
+ description: 'List of provider names to query (optional, uses all if not specified)',
463
+ },
464
+ model: {
465
+ type: 'string',
466
+ description: 'Specific model to use for all providers (optional)',
467
+ },
468
+ },
469
+ required: ['prompt'],
470
+ },
471
+ },
472
+ {
473
+ name: 'duck_council',
474
+ description: 'Get responses from all configured ducks (like a panel discussion)',
475
+ inputSchema: {
476
+ type: 'object',
477
+ properties: {
478
+ prompt: {
479
+ type: 'string',
480
+ description: 'The question for the duck council',
481
+ },
482
+ model: {
483
+ type: 'string',
484
+ description: 'Specific model to use for all ducks (optional)',
485
+ },
486
+ },
487
+ required: ['prompt'],
488
+ },
489
+ },
490
+ ];
491
+
492
+ // Add MCP-specific tools if enabled
493
+ if (this.mcpEnabled) {
494
+ baseTools.push(
495
+ {
496
+ name: 'get_pending_approvals',
497
+ description: 'Get list of pending MCP tool approvals from ducks',
498
+ inputSchema: {
499
+ type: 'object',
500
+ properties: {
501
+ duck: {
502
+ type: 'string',
503
+ description: 'Filter by duck name (optional)',
504
+ },
505
+ },
506
+ },
507
+ },
508
+ {
509
+ name: 'approve_mcp_request',
510
+ description: "Approve or deny a duck's MCP tool request",
511
+ inputSchema: {
512
+ type: 'object',
513
+ properties: {
514
+ approval_id: {
515
+ type: 'string',
516
+ description: 'The approval request ID',
517
+ },
518
+ decision: {
519
+ type: 'string',
520
+ enum: ['approve', 'deny'],
521
+ description: 'Whether to approve or deny the request',
522
+ },
523
+ reason: {
524
+ type: 'string',
525
+ description: 'Reason for denial (optional)',
526
+ },
527
+ },
528
+ required: ['approval_id', 'decision'],
529
+ },
530
+ },
531
+ {
532
+ name: 'mcp_status',
533
+ description: 'Get status of MCP bridge, servers, and pending approvals',
534
+ inputSchema: {
535
+ type: 'object',
536
+ properties: {},
537
+ },
538
+ }
539
+ );
540
+ }
541
+
542
+ return baseTools;
543
+ }
544
+
545
+ async start() {
546
+ // Only show welcome message when not running as MCP server
547
+ const isMCP = process.env.MCP_SERVER === 'true' || process.argv.includes('--mcp');
548
+ if (!isMCP) {
549
+ // eslint-disable-next-line no-console
550
+ console.log(duckArt.welcome);
551
+ // eslint-disable-next-line no-console
552
+ console.log('\n' + getRandomDuckMessage('startup'));
553
+ }
554
+
555
+ // Initialize MCP connections if enabled
556
+ if (this.mcpEnabled && this.mcpClientManager) {
557
+ try {
558
+ logger.info('Connecting to MCP servers...');
559
+ await this.mcpClientManager.initialize();
560
+ logger.info('MCP servers connected successfully');
561
+ } catch (error: unknown) {
562
+ const errorMessage = error instanceof Error ? error.message : String(error);
563
+ logger.error('Failed to connect to some MCP servers:', errorMessage);
564
+ logger.warn('Some MCP functionality may not be available');
565
+ }
566
+ }
567
+
568
+ // Start health monitoring
569
+ this.healthMonitor.startMonitoring(60000);
570
+
571
+ // Initial health check
572
+ await this.healthMonitor.performHealthChecks();
573
+
574
+ // Start the server
575
+ const transport = new StdioServerTransport();
576
+ await this.server.connect(transport);
577
+
578
+ if (this.mcpEnabled) {
579
+ logger.info('🦆 MCP Rubber Duck server with MCP bridge started successfully!');
580
+ } else {
581
+ logger.info('🦆 MCP Rubber Duck server started successfully!');
582
+ }
583
+ }
584
+
585
+ async stop() {
586
+ // Stop health monitoring
587
+ this.healthMonitor.stopMonitoring();
588
+
589
+ // Cleanup MCP resources
590
+ if (this.approvalService) {
591
+ this.approvalService.shutdown();
592
+ }
593
+
594
+ if (this.mcpClientManager) {
595
+ await this.mcpClientManager.disconnectAll();
596
+ }
597
+
598
+ // Stop the server
599
+ await this.server.close();
600
+
601
+ logger.info('Server stopped');
602
+ }
603
+ }