@jungjaehoon/mama-server 1.0.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/src/server.js ADDED
@@ -0,0 +1,290 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * MAMA MCP Server
5
+ *
6
+ * Memory-Augmented MCP Assistant - Standalone MCP Server
7
+ *
8
+ * This server provides MCP tools for decision tracking, semantic search,
9
+ * and decision graph navigation across Claude Code and Claude Desktop.
10
+ *
11
+ * Architecture:
12
+ * - Stdio transport (standard MCP pattern)
13
+ * - SQLite + sqlite-vec for decision storage
14
+ * - Transformers.js for local embeddings
15
+ * - No network dependencies (100% local)
16
+ *
17
+ * Usage:
18
+ * node src/server.js # Direct execution
19
+ * mama-server # Via bin (npm install -g)
20
+ * npx @jungjaehoon/mama-server # Via npx
21
+ */
22
+
23
+ const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
24
+ const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
25
+ const {
26
+ CallToolRequestSchema,
27
+ ListToolsRequestSchema,
28
+ } = require('@modelcontextprotocol/sdk/types.js');
29
+
30
+ // Import MAMA tools
31
+ const { saveDecisionTool } = require('./tools/save-decision.js');
32
+ const { recallDecisionTool } = require('./tools/recall-decision.js');
33
+ const { suggestDecisionTool } = require('./tools/suggest-decision.js');
34
+ const { listDecisionsTool } = require('./tools/list-decisions.js');
35
+ const { updateOutcomeTool } = require('./tools/update-outcome.js');
36
+ const { saveCheckpointTool, loadCheckpointTool } = require('./tools/checkpoint-tools.js');
37
+
38
+ // Import core modules
39
+ const { initDB } = require('./mama/db-manager.js');
40
+
41
+ /**
42
+ * MAMA MCP Server Class
43
+ */
44
+ class MAMAServer {
45
+ constructor() {
46
+ this.server = new Server(
47
+ {
48
+ name: 'mama-server',
49
+ version: '1.0.0',
50
+ },
51
+ {
52
+ capabilities: {
53
+ tools: {},
54
+ },
55
+ }
56
+ );
57
+
58
+ this.setupHandlers();
59
+ }
60
+
61
+ setupHandlers() {
62
+ // List available tools
63
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
64
+ tools: [
65
+ {
66
+ name: 'save_decision',
67
+ description: 'Save a decision or insight to MAMA\'s memory for future reference.',
68
+ inputSchema: {
69
+ type: 'object',
70
+ properties: {
71
+ topic: {
72
+ type: 'string',
73
+ description: 'Decision topic identifier (e.g., \'auth_strategy\'). Use lowercase with underscores.',
74
+ },
75
+ decision: {
76
+ type: 'string',
77
+ description: 'The decision made (e.g., \'Use JWT with refresh tokens\').',
78
+ },
79
+ reasoning: {
80
+ type: 'string',
81
+ description: 'Why this decision was made. REQUIRED - explain the context and rationale.',
82
+ },
83
+ confidence: {
84
+ type: 'number',
85
+ description: 'Confidence score 0.0-1.0. Default: 0.5',
86
+ minimum: 0,
87
+ maximum: 1,
88
+ },
89
+ type: {
90
+ type: 'string',
91
+ enum: ['user_decision', 'assistant_insight'],
92
+ description: 'Decision type. Default: \'user_decision\'',
93
+ },
94
+ outcome: {
95
+ type: 'string',
96
+ enum: ['pending', 'success', 'failure', 'partial', 'superseded'],
97
+ description: 'Outcome status. Default: \'pending\'',
98
+ },
99
+ },
100
+ required: ['topic', 'decision', 'reasoning'],
101
+ },
102
+ },
103
+ {
104
+ name: 'recall_decision',
105
+ description: 'Recall full decision history for a specific topic.',
106
+ inputSchema: {
107
+ type: 'object',
108
+ properties: {
109
+ topic: {
110
+ type: 'string',
111
+ description: 'Decision topic to recall',
112
+ },
113
+ },
114
+ required: ['topic'],
115
+ },
116
+ },
117
+ {
118
+ name: 'suggest_decision',
119
+ description: 'Auto-suggest relevant past decisions based on user question.',
120
+ inputSchema: {
121
+ type: 'object',
122
+ properties: {
123
+ userQuestion: {
124
+ type: 'string',
125
+ description: 'User\'s question or intent',
126
+ },
127
+ recencyWeight: {
128
+ type: 'number',
129
+ description: 'Weight for recency (0-1). Default: 0.3',
130
+ minimum: 0,
131
+ maximum: 1,
132
+ },
133
+ },
134
+ required: ['userQuestion'],
135
+ },
136
+ },
137
+ {
138
+ name: 'list_decisions',
139
+ description: 'List recent decisions with optional limit',
140
+ inputSchema: {
141
+ type: 'object',
142
+ properties: {
143
+ limit: {
144
+ type: 'number',
145
+ description: 'Maximum number of decisions (default: 10)',
146
+ },
147
+ },
148
+ },
149
+ },
150
+ {
151
+ name: 'update_outcome',
152
+ description: 'Update the outcome status of an existing decision',
153
+ inputSchema: {
154
+ type: 'object',
155
+ properties: {
156
+ topic: {
157
+ type: 'string',
158
+ description: 'Decision topic to update',
159
+ },
160
+ outcome: {
161
+ type: 'string',
162
+ enum: ['pending', 'success', 'failure', 'partial', 'superseded'],
163
+ description: 'New outcome status',
164
+ },
165
+ },
166
+ required: ['topic', 'outcome'],
167
+ },
168
+ },
169
+ {
170
+ name: 'save_checkpoint',
171
+ description: 'Save the current session state (checkpoint) to MAMA memory. Use this when ending a session or reaching a major milestone so work can be resumed later.',
172
+ inputSchema: {
173
+ type: 'object',
174
+ properties: {
175
+ summary: {
176
+ type: 'string',
177
+ description: 'Summary of the current session state, what was accomplished, and what is pending.',
178
+ },
179
+ open_files: {
180
+ type: 'array',
181
+ items: { type: 'string' },
182
+ description: 'List of currently relevant or open files.',
183
+ },
184
+ next_steps: {
185
+ type: 'string',
186
+ description: 'Clear instructions for the next session on what to do next.',
187
+ },
188
+ },
189
+ required: ['summary'],
190
+ },
191
+ },
192
+ {
193
+ name: 'load_checkpoint',
194
+ description: 'Load the latest active session checkpoint. Use this at the start of a new session to resume work seamlessly.',
195
+ inputSchema: {
196
+ type: 'object',
197
+ properties: {},
198
+ },
199
+ },
200
+ ],
201
+ }));
202
+
203
+ // Handle tool execution
204
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
205
+ const { name, arguments: args } = request.params;
206
+
207
+ try {
208
+ let result;
209
+
210
+ switch (name) {
211
+ case 'save_decision':
212
+ result = await saveDecisionTool.handler(args);
213
+ break;
214
+ case 'recall_decision':
215
+ result = await recallDecisionTool.handler(args);
216
+ break;
217
+ case 'suggest_decision':
218
+ result = await suggestDecisionTool.handler(args);
219
+ break;
220
+ case 'list_decisions':
221
+ result = await listDecisionsTool.handler(args);
222
+ break;
223
+ case 'update_outcome':
224
+ result = await updateOutcomeTool.handler(args);
225
+ break;
226
+ case 'save_checkpoint':
227
+ result = await saveCheckpointTool.handler(args);
228
+ break;
229
+ case 'load_checkpoint':
230
+ result = await loadCheckpointTool.handler(args);
231
+ break;
232
+ default:
233
+ throw new Error(`Unknown tool: ${name}`);
234
+ }
235
+
236
+ return {
237
+ content: [
238
+ {
239
+ type: 'text',
240
+ text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
241
+ },
242
+ ],
243
+ };
244
+ } catch (error) {
245
+ console.error('[MAMA MCP] Tool execution error:', error);
246
+ return {
247
+ content: [
248
+ {
249
+ type: 'text',
250
+ text: `Error: ${error.message}`,
251
+ },
252
+ ],
253
+ isError: true,
254
+ };
255
+ }
256
+ });
257
+ }
258
+
259
+ async start() {
260
+ try {
261
+ // Initialize database
262
+ console.error('[MAMA MCP] Initializing database...');
263
+ await initDB();
264
+ console.error('[MAMA MCP] Database initialized');
265
+
266
+ // Start server with stdio transport
267
+ const transport = new StdioServerTransport();
268
+ await this.server.connect(transport);
269
+
270
+ // Log to stderr (stdout is for MCP JSON-RPC)
271
+ console.error('[MAMA MCP] Server started successfully');
272
+ console.error('[MAMA MCP] Listening on stdio transport');
273
+ console.error('[MAMA MCP] Ready to accept connections');
274
+ } catch (error) {
275
+ console.error('[MAMA MCP] Failed to start server:', error);
276
+ process.exit(1);
277
+ }
278
+ }
279
+ }
280
+
281
+ // Start server if run directly
282
+ if (require.main === module) {
283
+ const server = new MAMAServer();
284
+ server.start().catch((error) => {
285
+ console.error('[MAMA MCP] Fatal error:', error);
286
+ process.exit(1);
287
+ });
288
+ }
289
+
290
+ module.exports = { MAMAServer };
@@ -0,0 +1,76 @@
1
+ const { saveCheckpoint, loadCheckpoint } = require('../mama/mama-api');
2
+
3
+ const saveCheckpointTool = {
4
+ name: 'save_checkpoint',
5
+ description: 'Save the current session state (checkpoint) to MAMA memory. Use this when ending a session or reaching a major milestone so work can be resumed later.',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ summary: {
10
+ type: 'string',
11
+ description: 'Summary of the current session state, what was accomplished, and what is pending.',
12
+ },
13
+ open_files: {
14
+ type: 'array',
15
+ items: { type: 'string' },
16
+ description: 'List of currently relevant or open files.',
17
+ },
18
+ next_steps: {
19
+ type: 'string',
20
+ description: 'Clear instructions for the next session on what to do next.',
21
+ },
22
+ },
23
+ required: ['summary'],
24
+ },
25
+ handler: async (args) => {
26
+ const { summary, open_files, next_steps } = args;
27
+ const id = await saveCheckpoint(summary, open_files, next_steps);
28
+ return {
29
+ content: [
30
+ {
31
+ type: 'text',
32
+ text: `✅ Checkpoint saved (ID: ${id})\nSummary: ${summary}`,
33
+ },
34
+ ],
35
+ };
36
+ },
37
+ };
38
+
39
+ const loadCheckpointTool = {
40
+ name: 'load_checkpoint',
41
+ description: 'Load the latest active session checkpoint. Use this at the start of a new session to resume work seamlessly.',
42
+ inputSchema: {
43
+ type: 'object',
44
+ properties: {},
45
+ },
46
+ handler: async () => {
47
+ const checkpoint = await loadCheckpoint();
48
+ if (!checkpoint) {
49
+ return {
50
+ content: [
51
+ {
52
+ type: 'text',
53
+ text: 'ℹ️ No active checkpoint found.',
54
+ },
55
+ ],
56
+ };
57
+ }
58
+
59
+ return {
60
+ content: [
61
+ {
62
+ type: 'text',
63
+ text: `🔄 Resuming Session (from ${new Date(checkpoint.timestamp).toLocaleString()})\n\n` +
64
+ `📝 Summary: ${checkpoint.summary}\n` +
65
+ `📂 Files: ${JSON.stringify(checkpoint.open_files)}\n` +
66
+ `👉 Next Steps: ${checkpoint.next_steps || 'None'}`,
67
+ },
68
+ ],
69
+ };
70
+ },
71
+ };
72
+
73
+ module.exports = {
74
+ saveCheckpointTool,
75
+ loadCheckpointTool,
76
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * MAMA Memory Tools
3
+ *
4
+ * Story M1.3: MCP Tool Surface Port
5
+ * Story M1.5: update_outcome tool added
6
+ * MCP tool wrappers for MAMA's memory system
7
+ *
8
+ * Tools:
9
+ * - save_decision: Save decisions/insights to memory ✅
10
+ * - recall_decision: Retrieve decision history by topic ✅
11
+ * - suggest_decision: Semantic search for relevant decisions ✅
12
+ * - list_decisions: List recent decisions chronologically ✅
13
+ * - update_outcome: Update decision outcome ✅
14
+ *
15
+ * @module tools
16
+ */
17
+
18
+ const { saveDecisionTool } = require('./save-decision.js');
19
+ const { recallDecisionTool } = require('./recall-decision.js');
20
+ const { suggestDecisionTool } = require('./suggest-decision.js');
21
+ const { listDecisionsTool } = require('./list-decisions.js');
22
+ const { updateOutcomeTool } = require('./update-outcome.js');
23
+ const { saveCheckpointTool, loadCheckpointTool } = require('./checkpoint-tools.js');
24
+
25
+ /**
26
+ * Create all MAMA memory tools
27
+ *
28
+ * Database location: ~/.claude/mama-memory.db (or MAMA_DB_PATH env var)
29
+ *
30
+ * @returns Object with tool definitions
31
+ */
32
+ function createMemoryTools() {
33
+ return {
34
+ save_decision: saveDecisionTool,
35
+ recall_decision: recallDecisionTool,
36
+ suggest_decision: suggestDecisionTool,
37
+ list_decisions: listDecisionsTool,
38
+ update_outcome: updateOutcomeTool,
39
+ save_checkpoint: saveCheckpointTool,
40
+ load_checkpoint: loadCheckpointTool,
41
+ };
42
+ }
43
+
44
+ // Export individual tool creators for testing
45
+ module.exports = {
46
+ createMemoryTools,
47
+ saveDecisionTool,
48
+ recallDecisionTool,
49
+ suggestDecisionTool,
50
+ listDecisionsTool,
51
+ updateOutcomeTool,
52
+ saveCheckpointTool,
53
+ loadCheckpointTool,
54
+ };
@@ -0,0 +1,76 @@
1
+ /**
2
+ * MCP Tool: list_decisions
3
+ *
4
+ * Lists recent decisions in chronological order.
5
+ * Returns formatted list with time, type, topic, preview, confidence, and status.
6
+ *
7
+ * Flow:
8
+ * 1. User (via Claude Desktop): "Show me recent decisions"
9
+ * 2. Claude: Calls list_decisions MCP tool
10
+ * 3. Tool: Validates input, calls mama.list()
11
+ * 4. mama.list(): Queries recent decisions + formats as markdown
12
+ * 5. Tool: Returns formatted markdown response
13
+ *
14
+ * @module list-decisions
15
+ */
16
+
17
+ const path = require('path');
18
+
19
+ // Import MAMA API from core directory
20
+ const mama = require('../mama/mama-api.js');
21
+
22
+ /**
23
+ * List decisions tool definition
24
+ */
25
+ const listDecisionsTool = {
26
+ name: 'list_decisions',
27
+ description:
28
+ 'List recent decisions in chronological order. Returns formatted list showing time, type (user/assistant), topic, preview, confidence, and status. Use this to see recent activity or find decisions by browsing.',
29
+ inputSchema: {
30
+ type: 'object',
31
+ properties: {
32
+ limit: {
33
+ type: 'number',
34
+ description: 'Maximum number of decisions to return (default: 20, max: 100)',
35
+ minimum: 1,
36
+ maximum: 100,
37
+ },
38
+ },
39
+ required: [],
40
+ },
41
+
42
+ async handler(params, context) {
43
+ const { limit = 20 } = params || {};
44
+
45
+ try {
46
+ // Validation: Limit range check
47
+ if (limit < 1 || limit > 100) {
48
+ return {
49
+ success: false,
50
+ message: '❌ Validation error: Limit must be between 1 and 100',
51
+ };
52
+ }
53
+
54
+ // Call MAMA API with markdown format for human display
55
+ // mama.list() defaults to JSON (LLM-first), but we need markdown for user display
56
+ const list = await mama.list({ limit, format: 'markdown' });
57
+
58
+ // Return success response with formatted list
59
+ return {
60
+ success: true,
61
+ list,
62
+ message: list, // For backward compatibility with MCP response format
63
+ };
64
+ } catch (error) {
65
+ // Error handling: Return user-friendly message
66
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
67
+
68
+ return {
69
+ success: false,
70
+ message: `❌ Failed to list decisions: ${errorMessage}`,
71
+ };
72
+ }
73
+ },
74
+ };
75
+
76
+ module.exports = { listDecisionsTool };
@@ -0,0 +1,75 @@
1
+ /**
2
+ * MCP Tool: recall_decision
3
+ *
4
+ * Story M1.3: MCP Tool - recall_decision (ported from mcp-server)
5
+ * Priority: P1 (Core Feature)
6
+ *
7
+ * Recalls full decision history for a specific topic.
8
+ * This is a wrapper around the existing mama.recall() API.
9
+ *
10
+ * Flow:
11
+ * 1. User (via Claude Desktop): "Recall my decision about auth strategy"
12
+ * 2. Claude: Calls recall_decision MCP tool
13
+ * 3. Tool: Validates input, calls mama.recall()
14
+ * 4. mama.recall(): Queries decision history + formats as markdown
15
+ * 5. Tool: Returns formatted markdown response
16
+ *
17
+ * @module recall-decision
18
+ */
19
+
20
+ const mama = require('../mama/mama-api.js');
21
+
22
+ /**
23
+ * Recall decision tool definition
24
+ */
25
+ const recallDecisionTool = {
26
+ name: 'recall_decision',
27
+ description:
28
+ 'Recall full decision history for a specific topic. Returns all past decisions on this topic in chronological order with reasoning, confidence, and outcomes. Use this when you need to review previous decisions, understand decision evolution, or check current position on a topic.\n\n⚡ GRAPH TRAVERSAL: When the same topic is reused across multiple decisions, this tool automatically shows the decision evolution chain (supersedes graph), enabling Learn/Unlearn/Relearn workflows.',
29
+ inputSchema: {
30
+ type: 'object',
31
+ properties: {
32
+ topic: {
33
+ type: 'string',
34
+ description:
35
+ "Decision topic to recall (e.g., 'auth_strategy', 'mesh_detail_choice'). Use the EXACT SAME topic name used in save_decision to see full decision evolution graph. Different topic names will show separate, disconnected decisions.",
36
+ },
37
+ },
38
+ required: ['topic'],
39
+ },
40
+
41
+ async handler(params, context) {
42
+ const { topic } = params || {};
43
+
44
+ try {
45
+ // Validation: Non-empty string check
46
+ if (!topic || typeof topic !== 'string' || topic.trim() === '') {
47
+ return {
48
+ success: false,
49
+ message: '❌ Validation error: Topic must be a non-empty string',
50
+ };
51
+ }
52
+
53
+ // Call MAMA API with markdown format for human display
54
+ // mama.recall() defaults to JSON (LLM-first), but we need markdown for user display
55
+ const history = await mama.recall(topic, { format: 'markdown' });
56
+
57
+ // Return success response with formatted history
58
+ return {
59
+ success: true,
60
+ history,
61
+ message: history, // For backward compatibility with MCP response format
62
+ };
63
+ } catch (error) {
64
+ // Error handling: Return user-friendly message
65
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
66
+
67
+ return {
68
+ success: false,
69
+ message: `❌ Failed to recall decisions: ${errorMessage}`,
70
+ };
71
+ }
72
+ },
73
+ };
74
+
75
+ module.exports = { recallDecisionTool };
@@ -0,0 +1,113 @@
1
+ /**
2
+ * MCP Tool: save_decision
3
+ *
4
+ * Story M1.3: MCP Tool - save_decision (ported from mcp-server)
5
+ * Priority: P1 (Core Feature)
6
+ *
7
+ * Saves decisions and insights to MAMA's memory for future reference.
8
+ *
9
+ * @module save-decision
10
+ */
11
+
12
+ const mama = require('../mama/mama-api.js');
13
+
14
+ /**
15
+ * Save decision tool definition
16
+ */
17
+ const saveDecisionTool = {
18
+ name: 'save_decision',
19
+ description:
20
+ "Save a decision or insight to MAMA's memory for future reference. Use this when the user explicitly wants to remember something important (e.g., architectural decisions, parameter choices, lessons learned). The decision will be stored with semantic embeddings for later retrieval.\n\n⚡ IMPORTANT - Graph Connectivity: Reuse the SAME topic name for related decisions to create decision graphs (supersedes/refines/contradicts edges). Example: Use 'auth_strategy' for all authentication decisions, not 'auth_strategy_v1', 'auth_strategy_v2'. This enables Learn/Unlearn/Relearn workflows.",
21
+ inputSchema: {
22
+ type: 'object',
23
+ properties: {
24
+ topic: {
25
+ type: 'string',
26
+ description:
27
+ "Decision topic identifier (e.g., 'auth_strategy', 'mesh_detail_choice'). Use lowercase with underscores. Max 200 characters.\n\n⚡ REUSE SAME TOPIC for related decisions to create supersedes edges.",
28
+ },
29
+ decision: {
30
+ type: 'string',
31
+ description: "The decision made (e.g., 'Use JWT with refresh tokens'). Max 2000 characters.",
32
+ },
33
+ reasoning: {
34
+ type: 'string',
35
+ description:
36
+ 'Why this decision was made. This is REQUIRED - never leave empty. Explain the context, alternatives considered, and rationale. IMPORTANT: Use English for better semantic search and relationship detection (e.g., use "instead of", "contrary to", "replaces" for conflicting decisions). Max 5000 characters.',
37
+ },
38
+ confidence: {
39
+ type: 'number',
40
+ description:
41
+ 'Confidence score 0.0-1.0. Use 0.9 for high confidence, 0.8 for medium, 0.5 for experimental. Default: 0.5',
42
+ minimum: 0,
43
+ maximum: 1,
44
+ },
45
+ type: {
46
+ type: 'string',
47
+ enum: ['user_decision', 'assistant_insight'],
48
+ description: "'user_decision' if user explicitly decided, 'assistant_insight' if this is Claude's suggestion. Default: 'user_decision'",
49
+ },
50
+ outcome: {
51
+ type: 'string',
52
+ enum: ['pending', 'success', 'failure', 'partial', 'superseded'],
53
+ description:
54
+ "Decision outcome status. Use 'pending' for new decisions (default), 'success' when confirmed working, 'failure' when approach failed.",
55
+ },
56
+ },
57
+ required: ['topic', 'decision', 'reasoning'],
58
+ },
59
+
60
+ async handler(params, context) {
61
+ const {
62
+ topic,
63
+ decision,
64
+ reasoning,
65
+ confidence = 0.5,
66
+ type = 'user_decision',
67
+ outcome = 'pending',
68
+ } = params || {};
69
+
70
+ try {
71
+ // Validation
72
+ if (!topic || !decision || !reasoning) {
73
+ return {
74
+ success: false,
75
+ message: '❌ Validation error: topic, decision, and reasoning are required',
76
+ };
77
+ }
78
+
79
+ if (topic.length > 200 || decision.length > 2000 || reasoning.length > 5000) {
80
+ return {
81
+ success: false,
82
+ message: '❌ Validation error: Field length exceeded (topic≤200, decision≤2000, reasoning≤5000)',
83
+ };
84
+ }
85
+
86
+ // Call MAMA API (mama.save will handle outcome mapping to DB format)
87
+ const result = await mama.save({
88
+ topic,
89
+ decision,
90
+ reasoning,
91
+ confidence,
92
+ user_involvement: type,
93
+ outcome,
94
+ });
95
+
96
+ return {
97
+ success: true,
98
+ decision_id: result.id,
99
+ topic: topic,
100
+ message: `✅ Decision saved successfully (ID: ${result.id})`,
101
+ recall_command: `To recall: mama.recall('${topic}')`,
102
+ };
103
+ } catch (error) {
104
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
105
+ return {
106
+ success: false,
107
+ message: `❌ Failed to save decision: ${errorMessage}`,
108
+ };
109
+ }
110
+ },
111
+ };
112
+
113
+ module.exports = { saveDecisionTool };