bunosh 0.4.14 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -565
- package/bunosh.js +32 -193
- package/index.js +4 -3
- package/package.json +18 -2
- package/src/error-formatter.js +80 -0
- package/src/formatters/console.js +5 -1
- package/src/io.js +0 -5
- package/src/printer.js +29 -9
- package/src/program.js +131 -343
- package/src/task.js +8 -1
- package/src/tasks/exec.js +4 -248
- package/src/tasks/fetch.js +2 -1
- package/src/tasks/shell.js +194 -119
- package/src/upgrade.js +135 -30
- package/src/mcp-server.js +0 -575
package/src/mcp-server.js
DELETED
|
@@ -1,575 +0,0 @@
|
|
|
1
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import {
|
|
4
|
-
CallToolRequestSchema,
|
|
5
|
-
ErrorCode,
|
|
6
|
-
ListToolsRequestSchema,
|
|
7
|
-
McpError,
|
|
8
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
-
import { processCommands } from './program.js';
|
|
10
|
-
|
|
11
|
-
// Global state for managing conversations and pending questions
|
|
12
|
-
const conversations = new Map();
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Convert BunoshCommand objects to MCP tools
|
|
16
|
-
* @param {Array<BunoshCommand>} parsedCommands - Array of parsed BunoshCommand objects
|
|
17
|
-
* @returns {Object} MCP tools object
|
|
18
|
-
*/
|
|
19
|
-
function createMcpTools(parsedCommands) {
|
|
20
|
-
const tools = {};
|
|
21
|
-
|
|
22
|
-
parsedCommands.forEach((command) => {
|
|
23
|
-
// Create input schema for the tool
|
|
24
|
-
const properties = {};
|
|
25
|
-
const required = [];
|
|
26
|
-
|
|
27
|
-
// Add positional arguments
|
|
28
|
-
Object.entries(command.args).forEach(([argName, defaultValue]) => {
|
|
29
|
-
properties[argName] = {
|
|
30
|
-
type: 'string',
|
|
31
|
-
description: `Argument: ${argName}`,
|
|
32
|
-
};
|
|
33
|
-
if (defaultValue === undefined) {
|
|
34
|
-
required.push(argName);
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Add options
|
|
39
|
-
Object.entries(command.opts).forEach(([optName, defaultValue]) => {
|
|
40
|
-
properties[optName] = {
|
|
41
|
-
type: typeof defaultValue === 'boolean' ? 'boolean' : 'string',
|
|
42
|
-
description: `Option: --${optName}`,
|
|
43
|
-
};
|
|
44
|
-
// Don't make options required as they have defaults
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
tools[command.fullName] = {
|
|
48
|
-
name: command.fullName,
|
|
49
|
-
description: command.comment || `Bunosh command: ${command.fullName}`,
|
|
50
|
-
inputSchema: {
|
|
51
|
-
type: 'object',
|
|
52
|
-
properties,
|
|
53
|
-
required: required.length > 0 ? required : undefined,
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// Add the answer tool for handling interactive questions
|
|
59
|
-
tools.answer = {
|
|
60
|
-
name: 'answer',
|
|
61
|
-
description: 'Provide an answer to a question asked by a Bunosh command',
|
|
62
|
-
inputSchema: {
|
|
63
|
-
type: 'object',
|
|
64
|
-
properties: {
|
|
65
|
-
conversationId: {
|
|
66
|
-
type: 'string',
|
|
67
|
-
description: 'The ID of the conversation to answer',
|
|
68
|
-
},
|
|
69
|
-
questionId: {
|
|
70
|
-
type: 'string',
|
|
71
|
-
description: 'The ID of the question to answer',
|
|
72
|
-
},
|
|
73
|
-
answer: {
|
|
74
|
-
oneOf: [
|
|
75
|
-
{ type: 'string' },
|
|
76
|
-
{ type: 'boolean' },
|
|
77
|
-
{ type: 'array', items: { type: 'string' } }
|
|
78
|
-
],
|
|
79
|
-
description: 'The answer to provide - can be string, boolean, or array of strings for multiple choice',
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
required: ['conversationId', 'questionId', 'answer'],
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
return tools;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Create a conversation state for tracking multi-question interactions
|
|
91
|
-
* @param {string} conversationId - Unique conversation identifier
|
|
92
|
-
* @param {BunoshCommand} command - The command being executed
|
|
93
|
-
* @param {Object} args - Command arguments
|
|
94
|
-
* @returns {Object} Conversation state object
|
|
95
|
-
*/
|
|
96
|
-
function createConversation(conversationId, command, args) {
|
|
97
|
-
const conversation = {
|
|
98
|
-
id: conversationId,
|
|
99
|
-
command,
|
|
100
|
-
args,
|
|
101
|
-
questions: [], // List of questions asked in this conversation
|
|
102
|
-
answers: {}, // Map of questionId to answer
|
|
103
|
-
currentQuestion: null,
|
|
104
|
-
output: [],
|
|
105
|
-
isComplete: false,
|
|
106
|
-
result: null,
|
|
107
|
-
startTime: Date.now()
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
conversations.set(conversationId, conversation);
|
|
111
|
-
return conversation;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Create an interactive ask function that works with conversation state
|
|
116
|
-
* @param {Object} conversation - The conversation state
|
|
117
|
-
* @returns {Function} Interactive ask function
|
|
118
|
-
*/
|
|
119
|
-
function createConversationAskFunction(conversation) {
|
|
120
|
-
return async (question, defaultValueOrOptions = {}, options = {}) => {
|
|
121
|
-
// Check if we already have an answer for this exact question text
|
|
122
|
-
// Look for ANY question with the same text that has an answer
|
|
123
|
-
// Important: Use 'in' operator to check if key exists, even if the value is falsy (like false)
|
|
124
|
-
const existingQuestionWithAnswer = conversation.questions.find(q => q.message === question && q.id in conversation.answers);
|
|
125
|
-
|
|
126
|
-
if (existingQuestionWithAnswer) {
|
|
127
|
-
return conversation.answers[existingQuestionWithAnswer.id];
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Generate a unique question ID
|
|
131
|
-
const questionId = `q_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
132
|
-
|
|
133
|
-
// Parse the question options to understand what type of answer is expected
|
|
134
|
-
let questionOptions = {};
|
|
135
|
-
let expectedType = 'string';
|
|
136
|
-
|
|
137
|
-
// Smart parameter detection (copied from original ask function)
|
|
138
|
-
if (defaultValueOrOptions !== null && typeof defaultValueOrOptions !== 'object') {
|
|
139
|
-
questionOptions.default = defaultValueOrOptions;
|
|
140
|
-
questionOptions = { ...questionOptions, ...options };
|
|
141
|
-
|
|
142
|
-
if (typeof defaultValueOrOptions === 'boolean') {
|
|
143
|
-
expectedType = 'boolean';
|
|
144
|
-
}
|
|
145
|
-
} else if (Array.isArray(defaultValueOrOptions)) {
|
|
146
|
-
questionOptions.choices = defaultValueOrOptions;
|
|
147
|
-
questionOptions = { ...questionOptions, ...options };
|
|
148
|
-
expectedType = 'choices';
|
|
149
|
-
} else {
|
|
150
|
-
questionOptions = { ...defaultValueOrOptions, ...options };
|
|
151
|
-
if (questionOptions.type === 'confirm') {
|
|
152
|
-
expectedType = 'boolean';
|
|
153
|
-
} else if (questionOptions.choices) {
|
|
154
|
-
expectedType = 'choices';
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Store the question data
|
|
159
|
-
const questionData = {
|
|
160
|
-
id: questionId,
|
|
161
|
-
conversationId: conversation.id,
|
|
162
|
-
message: question,
|
|
163
|
-
options: questionOptions,
|
|
164
|
-
expectedType,
|
|
165
|
-
choices: questionOptions.choices || null,
|
|
166
|
-
multiple: questionOptions.multiple || false,
|
|
167
|
-
timestamp: Date.now()
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
// Add question to conversation (only if it's not already there)
|
|
171
|
-
// Check if this exact question already exists
|
|
172
|
-
const existingQuestion = conversation.questions.find(q => q.message === question);
|
|
173
|
-
if (!existingQuestion) {
|
|
174
|
-
conversation.questions.push(questionData);
|
|
175
|
-
conversation.currentQuestion = questionData;
|
|
176
|
-
} else {
|
|
177
|
-
conversation.currentQuestion = existingQuestion;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Throw a special error to signal the MCP server that a question needs to be answered
|
|
181
|
-
const questionError = new Error(`INTERACTIVE_QUESTION:${JSON.stringify(questionData)}`);
|
|
182
|
-
questionError.code = 'INTERACTIVE_QUESTION';
|
|
183
|
-
throw questionError;
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Execute a Bunosh command with conversation-based multi-question support
|
|
189
|
-
* @param {BunoshCommand} command - Parsed BunoshCommand object
|
|
190
|
-
* @param {Object} args - Arguments from MCP tool call
|
|
191
|
-
* @param {string} [conversationId] - ID for resuming an existing conversation
|
|
192
|
-
* @returns {Promise<string>} Command output with task information
|
|
193
|
-
*/
|
|
194
|
-
async function executeBunoshCommand(command, args, conversationId = null) {
|
|
195
|
-
// Import task functions to access executed tasks
|
|
196
|
-
const { tasksExecuted } = await import('./task.js');
|
|
197
|
-
|
|
198
|
-
// Check if we're resuming an existing conversation
|
|
199
|
-
if (conversationId && conversations.has(conversationId)) {
|
|
200
|
-
const conversation = conversations.get(conversationId);
|
|
201
|
-
|
|
202
|
-
// Re-execute the command with the same conversation state
|
|
203
|
-
// The ask function will return pre-provided answers for already-answered questions
|
|
204
|
-
return executeCommandWithConversation(conversation);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Create new conversation
|
|
208
|
-
const newConversationId = conversationId || `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
209
|
-
const conversation = createConversation(newConversationId, command, args);
|
|
210
|
-
|
|
211
|
-
return executeCommandWithConversation(conversation);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Execute a command within a conversation context
|
|
216
|
-
* @param {Object} conversation - The conversation state
|
|
217
|
-
* @returns {Promise<string>} Command output or next question
|
|
218
|
-
*/
|
|
219
|
-
async function executeCommandWithConversation(conversation) {
|
|
220
|
-
// Import task functions to access executed tasks
|
|
221
|
-
const { tasksExecuted } = await import('./task.js');
|
|
222
|
-
|
|
223
|
-
// Capture stdout/stderr
|
|
224
|
-
const originalConsoleLog = console.log;
|
|
225
|
-
const originalConsoleError = console.error;
|
|
226
|
-
const output = [];
|
|
227
|
-
|
|
228
|
-
console.log = (...consoleArgs) => {
|
|
229
|
-
const message = consoleArgs.join(' ');
|
|
230
|
-
output.push(message);
|
|
231
|
-
conversation.output.push(message);
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
console.error = (...consoleArgs) => {
|
|
235
|
-
const message = `Error: ${consoleArgs.join(' ')}`;
|
|
236
|
-
output.push(message);
|
|
237
|
-
conversation.output.push(message);
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
try {
|
|
241
|
-
// Set up conversation-based ask function
|
|
242
|
-
globalThis._mcpAskFunction = createConversationAskFunction(conversation);
|
|
243
|
-
|
|
244
|
-
// Build arguments array using the parsed command information
|
|
245
|
-
const cmdArgs = [];
|
|
246
|
-
const optionsObject = {};
|
|
247
|
-
|
|
248
|
-
// Add positional arguments
|
|
249
|
-
Object.keys(conversation.command.args).forEach((argName) => {
|
|
250
|
-
if (conversation.args[argName] !== undefined) {
|
|
251
|
-
cmdArgs.push(conversation.args[argName]);
|
|
252
|
-
} else {
|
|
253
|
-
// Use default value from parsed command
|
|
254
|
-
cmdArgs.push(conversation.command.args[argName]);
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
// Handle options object
|
|
259
|
-
const optionKeys = Object.keys(conversation.command.opts);
|
|
260
|
-
if (optionKeys.length > 0) {
|
|
261
|
-
optionKeys.forEach((optName) => {
|
|
262
|
-
if (conversation.args[optName] !== undefined) {
|
|
263
|
-
optionsObject[optName] = conversation.args[optName];
|
|
264
|
-
} else {
|
|
265
|
-
// Use default value from parsed command
|
|
266
|
-
optionsObject[optName] = conversation.command.opts[optName];
|
|
267
|
-
}
|
|
268
|
-
});
|
|
269
|
-
cmdArgs.push(optionsObject);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Execute the command
|
|
273
|
-
const result = await conversation.command.function(...cmdArgs);
|
|
274
|
-
|
|
275
|
-
// Mark conversation as complete
|
|
276
|
-
conversation.isComplete = true;
|
|
277
|
-
conversation.result = result;
|
|
278
|
-
|
|
279
|
-
// Restore console and cleanup
|
|
280
|
-
console.log = originalConsoleLog;
|
|
281
|
-
console.error = originalConsoleError;
|
|
282
|
-
delete globalThis._mcpAskFunction;
|
|
283
|
-
|
|
284
|
-
// Get tasks executed during this command
|
|
285
|
-
const executedTasks = tasksExecuted.filter(task =>
|
|
286
|
-
task.startTime && task.startTime >= conversation.startTime
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
// Format response with task information
|
|
290
|
-
const response = {
|
|
291
|
-
output: output.join('\n'),
|
|
292
|
-
result: result,
|
|
293
|
-
tasks: executedTasks.map(task => ({
|
|
294
|
-
name: task.name,
|
|
295
|
-
status: task.status,
|
|
296
|
-
duration: task.duration,
|
|
297
|
-
output: task.result?.output || null
|
|
298
|
-
}))
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
// Add direct output if result is a string
|
|
302
|
-
if (result && typeof result === 'string') {
|
|
303
|
-
response.output += (response.output ? '\n' : '') + result;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Clean up completed conversation
|
|
307
|
-
conversations.delete(conversation.id);
|
|
308
|
-
|
|
309
|
-
// If no tasks were tracked, still return the output
|
|
310
|
-
if (executedTasks.length === 0) {
|
|
311
|
-
return response.output || 'Command executed successfully';
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Return JSON response with task information
|
|
315
|
-
return JSON.stringify(response, null, 2);
|
|
316
|
-
} catch (error) {
|
|
317
|
-
// Check if this is an interactive question
|
|
318
|
-
if (error.code === 'INTERACTIVE_QUESTION') {
|
|
319
|
-
// Extract question data from error message
|
|
320
|
-
const questionData = JSON.parse(error.message.split('INTERACTIVE_QUESTION:')[1]);
|
|
321
|
-
|
|
322
|
-
// Format the question for the AI to answer
|
|
323
|
-
let questionPrompt = `❓ **Question:** ${questionData.message}\n\n`;
|
|
324
|
-
|
|
325
|
-
if (questionData.expectedType === 'boolean') {
|
|
326
|
-
questionPrompt += `Please provide a boolean answer (true/false).`;
|
|
327
|
-
} else if (questionData.expectedType === 'choices') {
|
|
328
|
-
if (questionData.multiple) {
|
|
329
|
-
questionPrompt += `Please select one or more choices from: ${questionData.choices.join(', ')}\n`;
|
|
330
|
-
questionPrompt += `Provide the answer as an array of strings, e.g., ["option1", "option2"]`;
|
|
331
|
-
} else {
|
|
332
|
-
questionPrompt += `Please choose one option from: ${questionData.choices.join(', ')}\n`;
|
|
333
|
-
questionPrompt += `Provide the answer as a string (the exact choice).`;
|
|
334
|
-
}
|
|
335
|
-
} else {
|
|
336
|
-
questionPrompt += `Please provide a string answer.`;
|
|
337
|
-
if (questionData.options.default !== undefined) {
|
|
338
|
-
questionPrompt += ` Default value: ${questionData.options.default}`;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
questionPrompt += `\n\nUse the \`answer\` tool to respond with:\n`;
|
|
343
|
-
questionPrompt += `- conversationId: "${questionData.conversationId}"\n`;
|
|
344
|
-
questionPrompt += `- questionId: "${questionData.id}"\n`;
|
|
345
|
-
questionPrompt += `- answer: <your answer>`;
|
|
346
|
-
|
|
347
|
-
// Restore console and cleanup for now
|
|
348
|
-
console.log = originalConsoleLog;
|
|
349
|
-
console.error = originalConsoleError;
|
|
350
|
-
delete globalThis._mcpAskFunction;
|
|
351
|
-
|
|
352
|
-
// Return special response that prompts the AI to use the answer tool
|
|
353
|
-
return JSON.stringify({
|
|
354
|
-
interactive: true,
|
|
355
|
-
conversationId: questionData.conversationId,
|
|
356
|
-
questionId: questionData.id,
|
|
357
|
-
question: questionData.message,
|
|
358
|
-
prompt: questionPrompt,
|
|
359
|
-
expectedType: questionData.expectedType,
|
|
360
|
-
choices: questionData.choices,
|
|
361
|
-
multiple: questionData.multiple,
|
|
362
|
-
default: questionData.options.default,
|
|
363
|
-
progress: `Question ${conversation.questions.length} of ${conversation.questions.length + 1}`
|
|
364
|
-
}, null, 2);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Handle other errors
|
|
368
|
-
conversation.error = error;
|
|
369
|
-
|
|
370
|
-
// Get tasks executed before the error
|
|
371
|
-
const executedTasks = tasksExecuted.filter(task =>
|
|
372
|
-
task.startTime && task.startTime >= conversation.startTime
|
|
373
|
-
);
|
|
374
|
-
|
|
375
|
-
// Format error response with task information
|
|
376
|
-
const errorResponse = {
|
|
377
|
-
output: output.join('\n'),
|
|
378
|
-
error: error.message,
|
|
379
|
-
tasks: executedTasks.map(task => ({
|
|
380
|
-
name: task.name,
|
|
381
|
-
status: task.status,
|
|
382
|
-
duration: task.duration,
|
|
383
|
-
output: task.result?.output || null
|
|
384
|
-
}))
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
// Clean up conversation state
|
|
388
|
-
conversations.delete(conversation.id);
|
|
389
|
-
|
|
390
|
-
throw new Error(`Command execution failed: ${error.message}\n${JSON.stringify(errorResponse, null, 2)}`);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Handle the answer tool for responding to interactive questions
|
|
396
|
-
* @param {Object} args - Tool arguments containing conversationId, questionId, and answer
|
|
397
|
-
* @returns {Object} MCP response
|
|
398
|
-
*/
|
|
399
|
-
function handleAnswerTool(args) {
|
|
400
|
-
const { conversationId, questionId, answer } = args;
|
|
401
|
-
|
|
402
|
-
if (!conversationId || !questionId) {
|
|
403
|
-
throw new McpError(
|
|
404
|
-
ErrorCode.InvalidParams,
|
|
405
|
-
'Missing required parameters: conversationId and questionId'
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const conversation = conversations.get(conversationId);
|
|
410
|
-
if (!conversation) {
|
|
411
|
-
throw new McpError(
|
|
412
|
-
ErrorCode.InvalidParams,
|
|
413
|
-
`No conversation found with ID: ${conversationId}`
|
|
414
|
-
);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// Validate the answer based on question expectations
|
|
418
|
-
let validatedAnswer = answer;
|
|
419
|
-
const question = conversation.questions.find(q => q.id === questionId);
|
|
420
|
-
|
|
421
|
-
if (!question) {
|
|
422
|
-
throw new McpError(
|
|
423
|
-
ErrorCode.InvalidParams,
|
|
424
|
-
`No question found with ID: ${questionId} in conversation: ${conversationId}`
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (question.choices && Array.isArray(answer)) {
|
|
429
|
-
// For multiple choice questions, validate that all choices are valid
|
|
430
|
-
const invalidChoices = answer.filter(choice => !question.choices.includes(choice));
|
|
431
|
-
if (invalidChoices.length > 0) {
|
|
432
|
-
throw new McpError(
|
|
433
|
-
ErrorCode.InvalidParams,
|
|
434
|
-
`Invalid choices: ${invalidChoices.join(', ')}. Valid choices are: ${question.choices.join(', ')}`
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Store the answer
|
|
440
|
-
conversation.answers[questionId] = validatedAnswer;
|
|
441
|
-
|
|
442
|
-
return {
|
|
443
|
-
content: [
|
|
444
|
-
{
|
|
445
|
-
type: 'text',
|
|
446
|
-
text: `Answer received for question: ${question.message}`,
|
|
447
|
-
},
|
|
448
|
-
],
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* Continue a conversation after receiving an answer
|
|
454
|
-
* @param {string} conversationId - The conversation ID to continue
|
|
455
|
-
* @returns {Promise<string>} Command output or next question
|
|
456
|
-
*/
|
|
457
|
-
async function continueConversation(conversationId) {
|
|
458
|
-
const conversation = conversations.get(conversationId);
|
|
459
|
-
if (!conversation) {
|
|
460
|
-
throw new Error(`No conversation found with ID: ${conversationId}`);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Execute the command again, but this time it will use pre-provided answers
|
|
464
|
-
return executeCommandWithConversation(conversation);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* Create MCP server from Bunosh commands
|
|
469
|
-
* @param {Object} commands - Commands object from Bunoshfile
|
|
470
|
-
* @param {Object} sources - Sources object containing comments and metadata
|
|
471
|
-
* @returns {Server} Configured MCP server
|
|
472
|
-
*/
|
|
473
|
-
export function createMcpServer(commands, sources) {
|
|
474
|
-
const server = new Server(
|
|
475
|
-
{
|
|
476
|
-
name: 'bunosh',
|
|
477
|
-
version: '0.1.5',
|
|
478
|
-
},
|
|
479
|
-
{
|
|
480
|
-
capabilities: {
|
|
481
|
-
tools: {},
|
|
482
|
-
},
|
|
483
|
-
}
|
|
484
|
-
);
|
|
485
|
-
|
|
486
|
-
// Process commands using the existing logic from program.js
|
|
487
|
-
const parsedCommands = processCommands(commands, sources);
|
|
488
|
-
const tools = createMcpTools(parsedCommands);
|
|
489
|
-
|
|
490
|
-
// Create a map for quick command lookup
|
|
491
|
-
const commandMap = new Map(parsedCommands.map(cmd => [cmd.fullName, cmd]));
|
|
492
|
-
|
|
493
|
-
// List tools handler
|
|
494
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
495
|
-
return {
|
|
496
|
-
tools: Object.values(tools),
|
|
497
|
-
};
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
// Call tool handler
|
|
501
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
502
|
-
const { name, arguments: args } = request.params;
|
|
503
|
-
|
|
504
|
-
// Handle the answer tool separately
|
|
505
|
-
if (name === 'answer') {
|
|
506
|
-
const result = handleAnswerTool(args);
|
|
507
|
-
|
|
508
|
-
// After providing an answer, try to continue the conversation
|
|
509
|
-
const { conversationId } = args;
|
|
510
|
-
if (conversationId) {
|
|
511
|
-
try {
|
|
512
|
-
const continuationResult = await continueConversation(conversationId);
|
|
513
|
-
return {
|
|
514
|
-
content: [
|
|
515
|
-
result.content[0], // The "Answer received" message
|
|
516
|
-
{
|
|
517
|
-
type: 'text',
|
|
518
|
-
text: continuationResult,
|
|
519
|
-
},
|
|
520
|
-
],
|
|
521
|
-
};
|
|
522
|
-
} catch (error) {
|
|
523
|
-
// If continuation fails, just return the answer confirmation
|
|
524
|
-
return result;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
return result;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
const command = commandMap.get(name);
|
|
532
|
-
if (!command) {
|
|
533
|
-
throw new McpError(
|
|
534
|
-
ErrorCode.MethodNotFound,
|
|
535
|
-
`Unknown command: ${name}`
|
|
536
|
-
);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
try {
|
|
540
|
-
// Check if this is a continuation of a previous conversation
|
|
541
|
-
const conversationId = args.conversationId;
|
|
542
|
-
|
|
543
|
-
// Execute the Bunosh command with provided arguments
|
|
544
|
-
const result = await executeBunoshCommand(command, args, conversationId);
|
|
545
|
-
|
|
546
|
-
return {
|
|
547
|
-
content: [
|
|
548
|
-
{
|
|
549
|
-
type: 'text',
|
|
550
|
-
text: result,
|
|
551
|
-
},
|
|
552
|
-
],
|
|
553
|
-
};
|
|
554
|
-
} catch (error) {
|
|
555
|
-
throw new McpError(
|
|
556
|
-
ErrorCode.InternalError,
|
|
557
|
-
`Error executing command ${name}: ${error.message}`
|
|
558
|
-
);
|
|
559
|
-
}
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
return server;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
/**
|
|
566
|
-
* Start MCP server with stdio transport
|
|
567
|
-
* @param {Server} server - MCP server instance
|
|
568
|
-
*/
|
|
569
|
-
export async function startMcpServer(server) {
|
|
570
|
-
const transport = new StdioServerTransport();
|
|
571
|
-
await server.connect(transport);
|
|
572
|
-
|
|
573
|
-
// Server is now running and listening for MCP protocol messages
|
|
574
|
-
// No need to return anything as it will handle communication via stdio
|
|
575
|
-
}
|