auq-mcp-server 0.1.9 → 0.1.24
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 +24 -0
- package/dist/bin/auq.js +127 -7
- package/dist/package.json +15 -5
- package/dist/src/__tests__/schema-validation.test.js +32 -24
- package/dist/src/core/ask-user-questions.js +74 -0
- package/dist/src/server.js +11 -74
- package/dist/src/tui/components/Header.js +9 -1
- package/dist/src/tui/components/QuestionDisplay.js +10 -6
- package/dist/src/tui/components/ReviewScreen.js +6 -2
- package/dist/src/tui/components/StepperView.js +25 -3
- package/dist/src/tui/components/WaitingScreen.js +31 -4
- package/package.json +7 -1
- package/dist/__tests__/schema-validation.test.js +0 -137
- package/dist/__tests__/server.integration.test.js +0 -263
- package/dist/add.js +0 -1
- package/dist/add.test.js +0 -5
- package/dist/bin/test-session-menu.js +0 -28
- package/dist/bin/test-tabbar.js +0 -42
- package/dist/file-utils.js +0 -59
- package/dist/format/ResponseFormatter.js +0 -206
- package/dist/format/__tests__/ResponseFormatter.test.js +0 -380
- package/dist/server.js +0 -107
- package/dist/session/ResponseFormatter.js +0 -130
- package/dist/session/SessionManager.js +0 -474
- package/dist/session/__tests__/ResponseFormatter.test.js +0 -417
- package/dist/session/__tests__/SessionManager.test.js +0 -553
- package/dist/session/__tests__/atomic-operations.test.js +0 -345
- package/dist/session/__tests__/file-watcher.test.js +0 -311
- package/dist/session/__tests__/workflow.integration.test.js +0 -334
- package/dist/session/atomic-operations.js +0 -307
- package/dist/session/file-watcher.js +0 -218
- package/dist/session/index.js +0 -7
- package/dist/session/types.js +0 -20
- package/dist/session/utils.js +0 -125
- package/dist/session-manager.js +0 -171
- package/dist/session-watcher.js +0 -110
- package/dist/src/tui/components/SessionSelectionMenu.js +0 -151
- package/dist/tui/__tests__/session-watcher.test.js +0 -368
- package/dist/tui/session-watcher.js +0 -183
package/dist/server.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { FastMCP } from "fastmcp";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { SessionManager } from "./session/index.js";
|
|
4
|
-
// import { resolveSessionDirectory } from "./session/utils.js";
|
|
5
|
-
// Initialize session manager
|
|
6
|
-
const sessionManager = new SessionManager();
|
|
7
|
-
const server = new FastMCP({
|
|
8
|
-
instructions: "This MCP server provides a tool to ask users structured questions via the terminal. " +
|
|
9
|
-
"The ask_user_questions tool allows AI models to pause and gather direct user input through " +
|
|
10
|
-
"an interactive TUI, returning formatted responses for continued reasoning.",
|
|
11
|
-
name: "AskUserQuery",
|
|
12
|
-
version: "0.1.0",
|
|
13
|
-
});
|
|
14
|
-
// Define the question and option schemas
|
|
15
|
-
const OptionSchema = z.object({
|
|
16
|
-
description: z
|
|
17
|
-
.string()
|
|
18
|
-
.optional()
|
|
19
|
-
.describe("Optional explanatory note for this option"),
|
|
20
|
-
label: z.string().describe("The visible text of the choice"),
|
|
21
|
-
});
|
|
22
|
-
const QuestionSchema = z.object({
|
|
23
|
-
title: z.string().describe("Short 1-2 word summary for UI display"),
|
|
24
|
-
options: z
|
|
25
|
-
.array(OptionSchema)
|
|
26
|
-
.min(1)
|
|
27
|
-
.describe("Non-empty list of predefined answer choices"),
|
|
28
|
-
prompt: z.string().describe("The full question text"),
|
|
29
|
-
});
|
|
30
|
-
// Add the ask_user_questions tool
|
|
31
|
-
server.addTool({
|
|
32
|
-
annotations: {
|
|
33
|
-
openWorldHint: true, // This tool interacts with the user's terminal
|
|
34
|
-
readOnlyHint: false, // This tool waits for user input
|
|
35
|
-
title: "Ask User Questions",
|
|
36
|
-
},
|
|
37
|
-
description: "Ask the user one or more structured questions via an interactive terminal interface. " +
|
|
38
|
-
"Each question includes multiple-choice options and allows custom free-text responses. " +
|
|
39
|
-
"Returns a formatted summary of all questions and answers.",
|
|
40
|
-
execute: async (args, { log }) => {
|
|
41
|
-
try {
|
|
42
|
-
// Initialize session manager if not already done
|
|
43
|
-
await sessionManager.initialize();
|
|
44
|
-
// Clean up old sessions on startup (non-blocking)
|
|
45
|
-
sessionManager
|
|
46
|
-
.cleanupExpiredSessions()
|
|
47
|
-
.then((count) => {
|
|
48
|
-
if (count > 0) {
|
|
49
|
-
log.info(`Cleaned up ${count} expired session(s)`);
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
.catch((error) => {
|
|
53
|
-
log.warn("Cleanup failed:", { error: String(error) });
|
|
54
|
-
});
|
|
55
|
-
// Validate questions (using existing Zod schema validation)
|
|
56
|
-
if (!args.questions || args.questions.length === 0) {
|
|
57
|
-
throw new Error("At least one question is required");
|
|
58
|
-
}
|
|
59
|
-
// Convert Zod-validated questions to our internal Question type
|
|
60
|
-
const questions = args.questions.map((q) => ({
|
|
61
|
-
title: q.title,
|
|
62
|
-
options: q.options.map((opt) => ({
|
|
63
|
-
description: opt.description,
|
|
64
|
-
label: opt.label,
|
|
65
|
-
})),
|
|
66
|
-
prompt: q.prompt,
|
|
67
|
-
}));
|
|
68
|
-
log.info("Starting session and waiting for user answers...", {
|
|
69
|
-
questionCount: questions.length,
|
|
70
|
-
});
|
|
71
|
-
// Start complete session lifecycle - this will wait for user answers
|
|
72
|
-
const { formattedResponse, sessionId } = await sessionManager.startSession(questions);
|
|
73
|
-
log.info("Session completed successfully", { sessionId });
|
|
74
|
-
// Return formatted response to AI model
|
|
75
|
-
return {
|
|
76
|
-
content: [
|
|
77
|
-
{
|
|
78
|
-
text: formattedResponse,
|
|
79
|
-
type: "text",
|
|
80
|
-
},
|
|
81
|
-
],
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
log.error("Session failed", { error: String(error) });
|
|
86
|
-
return {
|
|
87
|
-
content: [
|
|
88
|
-
{
|
|
89
|
-
text: `Error in session: ${error}`,
|
|
90
|
-
type: "text",
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
name: "ask_user_questions",
|
|
97
|
-
parameters: z.object({
|
|
98
|
-
questions: z
|
|
99
|
-
.array(QuestionSchema)
|
|
100
|
-
.min(1)
|
|
101
|
-
.describe("Array of questions to ask the user"),
|
|
102
|
-
}),
|
|
103
|
-
});
|
|
104
|
-
// Start the server with stdio transport
|
|
105
|
-
server.start({
|
|
106
|
-
transportType: "stdio",
|
|
107
|
-
});
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Response Formatter
|
|
3
|
-
*
|
|
4
|
-
* Formats user answers according to PRD specification for returning to AI models
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* ResponseFormatter - Formats session answers into human-readable text
|
|
8
|
-
* according to the PRD specification
|
|
9
|
-
*/
|
|
10
|
-
export class ResponseFormatter {
|
|
11
|
-
/**
|
|
12
|
-
* Format user answers into PRD-compliant text response
|
|
13
|
-
*
|
|
14
|
-
* Format specification:
|
|
15
|
-
* - Header: "Here are the user's answers:"
|
|
16
|
-
* - Numbered questions: "1. {prompt}"
|
|
17
|
-
* - Arrow symbol for answers: "→ {label} — {description}"
|
|
18
|
-
* - Custom text: "→ Other: '{customText}'"
|
|
19
|
-
* - Double newline separation between questions
|
|
20
|
-
*
|
|
21
|
-
* @param answers - Session answer data containing user responses
|
|
22
|
-
* @param questions - Original questions asked to the user
|
|
23
|
-
* @returns Formatted text response ready to send to AI model
|
|
24
|
-
*/
|
|
25
|
-
static formatUserResponse(answers, questions) {
|
|
26
|
-
// Validate that we have matching questions and answers
|
|
27
|
-
if (answers.answers.length === 0) {
|
|
28
|
-
throw new Error("No answers provided in session");
|
|
29
|
-
}
|
|
30
|
-
if (questions.length === 0) {
|
|
31
|
-
throw new Error("No questions provided");
|
|
32
|
-
}
|
|
33
|
-
// Start with header
|
|
34
|
-
const lines = ["Here are the user's answers:", ""];
|
|
35
|
-
// Format each question and its answer
|
|
36
|
-
const formattedQuestions = [];
|
|
37
|
-
for (let i = 0; i < questions.length; i++) {
|
|
38
|
-
const question = questions[i];
|
|
39
|
-
const answer = answers.answers.find((a) => a.questionIndex === i);
|
|
40
|
-
if (!answer) {
|
|
41
|
-
// If no answer found for this question, skip it
|
|
42
|
-
// (This shouldn't happen in normal operation, but handle gracefully)
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
// Format the question and answer
|
|
46
|
-
const formattedQA = this.formatQuestion(question, answer, i + 1);
|
|
47
|
-
formattedQuestions.push(formattedQA);
|
|
48
|
-
}
|
|
49
|
-
// Join formatted questions with blank lines between them
|
|
50
|
-
lines.push(formattedQuestions.join("\n\n"));
|
|
51
|
-
return lines.join("\n");
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Validate that answers match the questions
|
|
55
|
-
*
|
|
56
|
-
* @param answers - Session answer data
|
|
57
|
-
* @param questions - Original questions
|
|
58
|
-
* @throws Error if validation fails
|
|
59
|
-
*/
|
|
60
|
-
static validateAnswers(answers, questions) {
|
|
61
|
-
// Check that we have answers
|
|
62
|
-
if (!answers || !answers.answers || answers.answers.length === 0) {
|
|
63
|
-
throw new Error("No answers provided");
|
|
64
|
-
}
|
|
65
|
-
// Check that we have questions
|
|
66
|
-
if (!questions || questions.length === 0) {
|
|
67
|
-
throw new Error("No questions provided");
|
|
68
|
-
}
|
|
69
|
-
// Check each answer references a valid question
|
|
70
|
-
for (const answer of answers.answers) {
|
|
71
|
-
if (answer.questionIndex < 0 ||
|
|
72
|
-
answer.questionIndex >= questions.length) {
|
|
73
|
-
throw new Error(`Answer references invalid question index: ${answer.questionIndex}`);
|
|
74
|
-
}
|
|
75
|
-
// Check that answer has either selectedOption or customText
|
|
76
|
-
if (!answer.selectedOption && !answer.customText) {
|
|
77
|
-
throw new Error(`Answer for question ${answer.questionIndex} has neither selectedOption nor customText`);
|
|
78
|
-
}
|
|
79
|
-
// If selectedOption is provided, verify it exists in the question's options
|
|
80
|
-
if (answer.selectedOption) {
|
|
81
|
-
const question = questions[answer.questionIndex];
|
|
82
|
-
const optionExists = question.options.some((opt) => opt.label === answer.selectedOption);
|
|
83
|
-
if (!optionExists) {
|
|
84
|
-
throw new Error(`Answer for question ${answer.questionIndex} references non-existent option: ${answer.selectedOption}`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Format a single question and its answer
|
|
91
|
-
*
|
|
92
|
-
* @param question - The question data
|
|
93
|
-
* @param answer - The user's answer
|
|
94
|
-
* @param index - Question number (1-indexed for display)
|
|
95
|
-
* @returns Formatted string for this question/answer pair
|
|
96
|
-
*/
|
|
97
|
-
static formatQuestion(question, answer, index) {
|
|
98
|
-
const lines = [];
|
|
99
|
-
// Add question with number
|
|
100
|
-
lines.push(`${index}. ${question.prompt}`);
|
|
101
|
-
// Format the answer
|
|
102
|
-
if (answer.customText) {
|
|
103
|
-
// Custom text answer - escape single quotes for display
|
|
104
|
-
const escapedText = answer.customText.replace(/'/g, "\\'");
|
|
105
|
-
lines.push(`→ Other: '${escapedText}'`);
|
|
106
|
-
}
|
|
107
|
-
else if (answer.selectedOption) {
|
|
108
|
-
// Selected option - find the option details
|
|
109
|
-
const option = question.options.find((opt) => opt.label === answer.selectedOption);
|
|
110
|
-
if (option) {
|
|
111
|
-
// Format with description if available
|
|
112
|
-
if (option.description) {
|
|
113
|
-
lines.push(`→ ${option.label} — ${option.description}`);
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
lines.push(`→ ${option.label}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
// Option not found - shouldn't happen, but handle gracefully
|
|
121
|
-
lines.push(`→ ${answer.selectedOption}`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
// No answer provided - this shouldn't happen
|
|
126
|
-
lines.push("→ No answer provided");
|
|
127
|
-
}
|
|
128
|
-
return lines.join("\n");
|
|
129
|
-
}
|
|
130
|
-
}
|