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.
- package/.dockerignore +19 -0
- package/.env.desktop.example +145 -0
- package/.env.example +45 -0
- package/.env.pi.example +106 -0
- package/.env.template +165 -0
- package/.eslintrc.json +40 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +65 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +58 -0
- package/.github/ISSUE_TEMPLATE/question.md +67 -0
- package/.github/pull_request_template.md +111 -0
- package/.github/workflows/docker-build.yml +138 -0
- package/.github/workflows/release.yml +182 -0
- package/.github/workflows/security.yml +141 -0
- package/.github/workflows/semantic-release.yml +89 -0
- package/.prettierrc +10 -0
- package/.releaserc.json +66 -0
- package/CHANGELOG.md +95 -0
- package/CONTRIBUTING.md +242 -0
- package/Dockerfile +62 -0
- package/LICENSE +21 -0
- package/README.md +803 -0
- package/audit-ci.json +8 -0
- package/config/claude_desktop.json +14 -0
- package/config/config.example.json +91 -0
- package/dist/config/config.d.ts +51 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +301 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/types.d.ts +356 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +41 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +109 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/duck-provider-enhanced.d.ts +29 -0
- package/dist/providers/duck-provider-enhanced.d.ts.map +1 -0
- package/dist/providers/duck-provider-enhanced.js +230 -0
- package/dist/providers/duck-provider-enhanced.js.map +1 -0
- package/dist/providers/enhanced-manager.d.ts +54 -0
- package/dist/providers/enhanced-manager.d.ts.map +1 -0
- package/dist/providers/enhanced-manager.js +217 -0
- package/dist/providers/enhanced-manager.js.map +1 -0
- package/dist/providers/manager.d.ts +28 -0
- package/dist/providers/manager.d.ts.map +1 -0
- package/dist/providers/manager.js +204 -0
- package/dist/providers/manager.js.map +1 -0
- package/dist/providers/provider.d.ts +29 -0
- package/dist/providers/provider.d.ts.map +1 -0
- package/dist/providers/provider.js +179 -0
- package/dist/providers/provider.js.map +1 -0
- package/dist/providers/types.d.ts +69 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/server.d.ts +24 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +501 -0
- package/dist/server.js.map +1 -0
- package/dist/services/approval.d.ts +44 -0
- package/dist/services/approval.d.ts.map +1 -0
- package/dist/services/approval.js +159 -0
- package/dist/services/approval.js.map +1 -0
- package/dist/services/cache.d.ts +21 -0
- package/dist/services/cache.d.ts.map +1 -0
- package/dist/services/cache.js +63 -0
- package/dist/services/cache.js.map +1 -0
- package/dist/services/conversation.d.ts +24 -0
- package/dist/services/conversation.d.ts.map +1 -0
- package/dist/services/conversation.js +108 -0
- package/dist/services/conversation.js.map +1 -0
- package/dist/services/function-bridge.d.ts +41 -0
- package/dist/services/function-bridge.d.ts.map +1 -0
- package/dist/services/function-bridge.js +259 -0
- package/dist/services/function-bridge.js.map +1 -0
- package/dist/services/health.d.ts +17 -0
- package/dist/services/health.d.ts.map +1 -0
- package/dist/services/health.js +77 -0
- package/dist/services/health.js.map +1 -0
- package/dist/services/mcp-client-manager.d.ts +49 -0
- package/dist/services/mcp-client-manager.d.ts.map +1 -0
- package/dist/services/mcp-client-manager.js +279 -0
- package/dist/services/mcp-client-manager.js.map +1 -0
- package/dist/tools/approve-mcp-request.d.ts +9 -0
- package/dist/tools/approve-mcp-request.d.ts.map +1 -0
- package/dist/tools/approve-mcp-request.js +111 -0
- package/dist/tools/approve-mcp-request.js.map +1 -0
- package/dist/tools/ask-duck.d.ts +9 -0
- package/dist/tools/ask-duck.d.ts.map +1 -0
- package/dist/tools/ask-duck.js +43 -0
- package/dist/tools/ask-duck.js.map +1 -0
- package/dist/tools/chat-duck.d.ts +9 -0
- package/dist/tools/chat-duck.d.ts.map +1 -0
- package/dist/tools/chat-duck.js +57 -0
- package/dist/tools/chat-duck.js.map +1 -0
- package/dist/tools/clear-conversations.d.ts +8 -0
- package/dist/tools/clear-conversations.d.ts.map +1 -0
- package/dist/tools/clear-conversations.js +17 -0
- package/dist/tools/clear-conversations.js.map +1 -0
- package/dist/tools/compare-ducks.d.ts +8 -0
- package/dist/tools/compare-ducks.d.ts.map +1 -0
- package/dist/tools/compare-ducks.js +49 -0
- package/dist/tools/compare-ducks.js.map +1 -0
- package/dist/tools/duck-council.d.ts +8 -0
- package/dist/tools/duck-council.d.ts.map +1 -0
- package/dist/tools/duck-council.js +69 -0
- package/dist/tools/duck-council.js.map +1 -0
- package/dist/tools/get-pending-approvals.d.ts +15 -0
- package/dist/tools/get-pending-approvals.d.ts.map +1 -0
- package/dist/tools/get-pending-approvals.js +74 -0
- package/dist/tools/get-pending-approvals.js.map +1 -0
- package/dist/tools/list-ducks.d.ts +9 -0
- package/dist/tools/list-ducks.d.ts.map +1 -0
- package/dist/tools/list-ducks.js +47 -0
- package/dist/tools/list-ducks.js.map +1 -0
- package/dist/tools/list-models.d.ts +8 -0
- package/dist/tools/list-models.d.ts.map +1 -0
- package/dist/tools/list-models.js +72 -0
- package/dist/tools/list-models.js.map +1 -0
- package/dist/tools/mcp-status.d.ts +17 -0
- package/dist/tools/mcp-status.d.ts.map +1 -0
- package/dist/tools/mcp-status.js +100 -0
- package/dist/tools/mcp-status.js.map +1 -0
- package/dist/utils/ascii-art.d.ts +19 -0
- package/dist/utils/ascii-art.d.ts.map +1 -0
- package/dist/utils/ascii-art.js +73 -0
- package/dist/utils/ascii-art.js.map +1 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +86 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/safe-logger.d.ts +23 -0
- package/dist/utils/safe-logger.d.ts.map +1 -0
- package/dist/utils/safe-logger.js +145 -0
- package/dist/utils/safe-logger.js.map +1 -0
- package/docker-compose.yml +161 -0
- package/jest.config.js +26 -0
- package/package.json +65 -0
- package/scripts/build-multiarch.sh +290 -0
- package/scripts/deploy-raspbian.sh +410 -0
- package/scripts/deploy.sh +322 -0
- package/scripts/gh-deploy.sh +343 -0
- package/scripts/setup-docker-raspbian.sh +530 -0
- package/server.json +8 -0
- package/src/config/config.ts +357 -0
- package/src/config/types.ts +89 -0
- package/src/index.ts +114 -0
- package/src/providers/duck-provider-enhanced.ts +294 -0
- package/src/providers/enhanced-manager.ts +290 -0
- package/src/providers/manager.ts +257 -0
- package/src/providers/provider.ts +207 -0
- package/src/providers/types.ts +78 -0
- package/src/server.ts +603 -0
- package/src/services/approval.ts +225 -0
- package/src/services/cache.ts +79 -0
- package/src/services/conversation.ts +146 -0
- package/src/services/function-bridge.ts +329 -0
- package/src/services/health.ts +107 -0
- package/src/services/mcp-client-manager.ts +362 -0
- package/src/tools/approve-mcp-request.ts +126 -0
- package/src/tools/ask-duck.ts +74 -0
- package/src/tools/chat-duck.ts +82 -0
- package/src/tools/clear-conversations.ts +24 -0
- package/src/tools/compare-ducks.ts +67 -0
- package/src/tools/duck-council.ts +88 -0
- package/src/tools/get-pending-approvals.ts +90 -0
- package/src/tools/list-ducks.ts +65 -0
- package/src/tools/list-models.ts +101 -0
- package/src/tools/mcp-status.ts +117 -0
- package/src/utils/ascii-art.ts +85 -0
- package/src/utils/logger.ts +116 -0
- package/src/utils/safe-logger.ts +165 -0
- package/systemd/mcp-rubber-duck-with-ollama.service +55 -0
- package/systemd/mcp-rubber-duck.service +58 -0
- package/test-functionality.js +147 -0
- package/test-mcp-interface.js +221 -0
- package/tests/ascii-art.test.ts +36 -0
- package/tests/config.test.ts +239 -0
- package/tests/conversation.test.ts +308 -0
- package/tests/mcp-bridge.test.ts +291 -0
- package/tests/providers.test.ts +269 -0
- package/tests/tools/clear-conversations.test.ts +163 -0
- 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
|
+
}
|