@mndrk/memx 0.3.3 → 0.3.4
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 +98 -77
- package/coverage/clover.xml +1160 -0
- package/coverage/coverage-final.json +3 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +131 -0
- package/coverage/lcov-report/index.js.html +7255 -0
- package/coverage/lcov-report/mcp.js.html +1009 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +2017 -0
- package/index.js +651 -243
- package/package.json +24 -2
- package/test/additional.test.js +373 -0
- package/test/branches.test.js +247 -0
- package/test/commands.test.js +663 -0
- package/test/context.test.js +185 -0
- package/test/coverage.test.js +366 -0
- package/test/dispatch.test.js +220 -0
- package/test/edge-coverage.test.js +250 -0
- package/test/edge.test.js +434 -0
- package/test/final-coverage.test.js +316 -0
- package/test/final-edges.test.js +199 -0
- package/test/init-local.test.js +316 -0
- package/test/init.test.js +122 -0
- package/test/interactive.test.js +229 -0
- package/test/main-dispatch.test.js +164 -0
- package/test/main-full.test.js +590 -0
- package/test/main.test.js +197 -0
- package/test/mcp-server.test.js +320 -0
- package/test/mcp.test.js +288 -0
- package/test/more.test.js +312 -0
- package/test/new.test.js +175 -0
- package/test/skill.test.js +247 -0
- package/test/tasks-interactive.test.js +243 -0
- package/test/utils.test.js +367 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for main function and help
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
// Mock child_process before requiring index.js
|
|
10
|
+
jest.mock('child_process', () => ({
|
|
11
|
+
execSync: jest.fn(),
|
|
12
|
+
spawnSync: jest.fn(() => ({ status: 0, stdout: '', stderr: '' })),
|
|
13
|
+
spawn: jest.fn()
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
// Mock readline
|
|
17
|
+
jest.mock('readline', () => ({
|
|
18
|
+
createInterface: jest.fn(() => ({
|
|
19
|
+
question: jest.fn((q, cb) => cb('')),
|
|
20
|
+
close: jest.fn(),
|
|
21
|
+
on: jest.fn()
|
|
22
|
+
}))
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
showHelp,
|
|
27
|
+
showMCPConfig,
|
|
28
|
+
startMCPServer
|
|
29
|
+
} = require('../index.js');
|
|
30
|
+
|
|
31
|
+
const { spawn } = require('child_process');
|
|
32
|
+
|
|
33
|
+
describe('showHelp', () => {
|
|
34
|
+
let consoleSpy;
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
consoleSpy.mockRestore();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('displays help message', () => {
|
|
45
|
+
showHelp();
|
|
46
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
47
|
+
expect(output).toContain('mem');
|
|
48
|
+
expect(output).toContain('Persistent memory');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('shows LIFECYCLE section', () => {
|
|
52
|
+
showHelp();
|
|
53
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
54
|
+
expect(output).toContain('LIFECYCLE');
|
|
55
|
+
expect(output).toContain('init');
|
|
56
|
+
expect(output).toContain('status');
|
|
57
|
+
expect(output).toContain('done');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('shows PROGRESS section', () => {
|
|
61
|
+
showHelp();
|
|
62
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
63
|
+
expect(output).toContain('PROGRESS');
|
|
64
|
+
expect(output).toContain('goal');
|
|
65
|
+
expect(output).toContain('next');
|
|
66
|
+
expect(output).toContain('checkpoint');
|
|
67
|
+
expect(output).toContain('stuck');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('shows LEARNING section', () => {
|
|
71
|
+
showHelp();
|
|
72
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
73
|
+
expect(output).toContain('LEARNING');
|
|
74
|
+
expect(output).toContain('learn');
|
|
75
|
+
expect(output).toContain('playbook');
|
|
76
|
+
expect(output).toContain('promote');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('shows QUERY section', () => {
|
|
80
|
+
showHelp();
|
|
81
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
82
|
+
expect(output).toContain('QUERY');
|
|
83
|
+
expect(output).toContain('context');
|
|
84
|
+
expect(output).toContain('history');
|
|
85
|
+
expect(output).toContain('query');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('shows TASKS section', () => {
|
|
89
|
+
showHelp();
|
|
90
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
91
|
+
expect(output).toContain('TASKS');
|
|
92
|
+
expect(output).toContain('tasks');
|
|
93
|
+
expect(output).toContain('switch');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('shows PRIMITIVES section', () => {
|
|
97
|
+
showHelp();
|
|
98
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
99
|
+
expect(output).toContain('SYNC');
|
|
100
|
+
expect(output).toContain('sync');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('shows INTEGRATION section', () => {
|
|
104
|
+
showHelp();
|
|
105
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
106
|
+
expect(output).toContain('INTEGRATION');
|
|
107
|
+
expect(output).toContain('skill');
|
|
108
|
+
expect(output).toContain('mcp');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('shows EXAMPLES section', () => {
|
|
112
|
+
showHelp();
|
|
113
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
114
|
+
expect(output).toContain('EXAMPLES');
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('showMCPConfig', () => {
|
|
119
|
+
let consoleSpy;
|
|
120
|
+
|
|
121
|
+
beforeEach(() => {
|
|
122
|
+
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
afterEach(() => {
|
|
126
|
+
consoleSpy.mockRestore();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('shows MCP configuration', () => {
|
|
130
|
+
showMCPConfig();
|
|
131
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
132
|
+
expect(output).toContain('MCP Server Configuration');
|
|
133
|
+
expect(output).toContain('mcpServers');
|
|
134
|
+
expect(output).toContain('mem');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('shows Claude Desktop config path', () => {
|
|
138
|
+
showMCPConfig();
|
|
139
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
140
|
+
expect(output).toContain('Claude Desktop');
|
|
141
|
+
expect(output).toContain('claude_desktop_config.json');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('shows project-specific config example', () => {
|
|
145
|
+
showMCPConfig();
|
|
146
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
147
|
+
expect(output).toContain('--dir');
|
|
148
|
+
expect(output).toContain('/path/to/your/project');
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('startMCPServer', () => {
|
|
153
|
+
let mockChild;
|
|
154
|
+
|
|
155
|
+
beforeEach(() => {
|
|
156
|
+
mockChild = {
|
|
157
|
+
on: jest.fn()
|
|
158
|
+
};
|
|
159
|
+
spawn.mockReturnValue(mockChild);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
afterEach(() => {
|
|
163
|
+
spawn.mockClear();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('spawns MCP server process', () => {
|
|
167
|
+
startMCPServer(['mcp']);
|
|
168
|
+
|
|
169
|
+
expect(spawn).toHaveBeenCalledWith(
|
|
170
|
+
'node',
|
|
171
|
+
expect.arrayContaining([expect.stringContaining('mcp.js')]),
|
|
172
|
+
expect.objectContaining({ stdio: 'inherit' })
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('passes through arguments', () => {
|
|
177
|
+
startMCPServer(['mcp', '--dir', '/test/path']);
|
|
178
|
+
|
|
179
|
+
expect(spawn).toHaveBeenCalledWith(
|
|
180
|
+
'node',
|
|
181
|
+
expect.arrayContaining(['--dir', '/test/path']),
|
|
182
|
+
expect.any(Object)
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('sets up error handler', () => {
|
|
187
|
+
startMCPServer(['mcp']);
|
|
188
|
+
|
|
189
|
+
expect(mockChild.on).toHaveBeenCalledWith('error', expect.any(Function));
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('sets up exit handler', () => {
|
|
193
|
+
startMCPServer(['mcp']);
|
|
194
|
+
|
|
195
|
+
expect(mockChild.on).toHaveBeenCalledWith('exit', expect.any(Function));
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for MCP server start() and related functions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const EventEmitter = require('events');
|
|
9
|
+
|
|
10
|
+
// Create mock readline interface
|
|
11
|
+
class MockReadlineInterface extends EventEmitter {
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
this.closed = false;
|
|
15
|
+
}
|
|
16
|
+
close() {
|
|
17
|
+
this.closed = true;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let mockRlInstance = null;
|
|
22
|
+
|
|
23
|
+
// Mock child_process
|
|
24
|
+
jest.mock('child_process', () => ({
|
|
25
|
+
execSync: jest.fn(),
|
|
26
|
+
spawnSync: jest.fn(() => ({ status: 0, stdout: '', stderr: '' })),
|
|
27
|
+
spawn: jest.fn()
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
// Mock readline with controllable interface
|
|
31
|
+
jest.mock('readline', () => ({
|
|
32
|
+
createInterface: jest.fn(() => {
|
|
33
|
+
mockRlInstance = new MockReadlineInterface();
|
|
34
|
+
return mockRlInstance;
|
|
35
|
+
})
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const { MCPServer } = require('../mcp.js');
|
|
39
|
+
const { spawnSync, spawn } = require('child_process');
|
|
40
|
+
|
|
41
|
+
const testDir = path.join(os.tmpdir(), 'memx-test-mcp-server-' + Date.now());
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
fs.mkdirSync(path.join(testDir, '.mem', '.git'), { recursive: true });
|
|
45
|
+
spawnSync.mockReset();
|
|
46
|
+
spawnSync.mockReturnValue({ status: 0, stdout: '', stderr: '' });
|
|
47
|
+
spawn.mockReset();
|
|
48
|
+
mockRlInstance = null;
|
|
49
|
+
jest.spyOn(console, 'log').mockImplementation();
|
|
50
|
+
jest.spyOn(console, 'error').mockImplementation();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
if (fs.existsSync(testDir)) {
|
|
55
|
+
fs.rmSync(testDir, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
jest.restoreAllMocks();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('MCPServer start()', () => {
|
|
61
|
+
test('starts server and handles valid JSON-RPC message', () => {
|
|
62
|
+
const server = new MCPServer(testDir);
|
|
63
|
+
server.start();
|
|
64
|
+
|
|
65
|
+
// Simulate receiving a valid message
|
|
66
|
+
const message = JSON.stringify({
|
|
67
|
+
jsonrpc: '2.0',
|
|
68
|
+
id: 1,
|
|
69
|
+
method: 'initialize',
|
|
70
|
+
params: {}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
mockRlInstance.emit('line', message);
|
|
74
|
+
|
|
75
|
+
expect(console.log).toHaveBeenCalled();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('handles parse error for invalid JSON', () => {
|
|
79
|
+
const server = new MCPServer(testDir);
|
|
80
|
+
server.start();
|
|
81
|
+
|
|
82
|
+
// Simulate receiving invalid JSON
|
|
83
|
+
mockRlInstance.emit('line', 'not valid json');
|
|
84
|
+
|
|
85
|
+
// Should log parse error response
|
|
86
|
+
const calls = console.log.mock.calls;
|
|
87
|
+
const lastCall = calls[calls.length - 1][0];
|
|
88
|
+
const response = JSON.parse(lastCall);
|
|
89
|
+
expect(response.error.code).toBe(-32700);
|
|
90
|
+
expect(response.error.message).toBe('Parse error');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('handles tools/list request', () => {
|
|
94
|
+
const server = new MCPServer(testDir);
|
|
95
|
+
server.start();
|
|
96
|
+
|
|
97
|
+
const message = JSON.stringify({
|
|
98
|
+
jsonrpc: '2.0',
|
|
99
|
+
id: 2,
|
|
100
|
+
method: 'tools/list',
|
|
101
|
+
params: {}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
mockRlInstance.emit('line', message);
|
|
105
|
+
|
|
106
|
+
const calls = console.log.mock.calls;
|
|
107
|
+
const lastCall = calls[calls.length - 1][0];
|
|
108
|
+
const response = JSON.parse(lastCall);
|
|
109
|
+
expect(response.result.tools).toBeDefined();
|
|
110
|
+
expect(Array.isArray(response.result.tools)).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('handles tools/call for mem_status', () => {
|
|
114
|
+
spawnSync.mockReturnValue({ status: 0, stdout: 'task/test', stderr: '' });
|
|
115
|
+
|
|
116
|
+
// Create necessary files
|
|
117
|
+
fs.writeFileSync(
|
|
118
|
+
path.join(testDir, '.mem', 'goal.md'),
|
|
119
|
+
'---\ntask: test\n---\n\n# Goal\n\nTest goal'
|
|
120
|
+
);
|
|
121
|
+
fs.writeFileSync(
|
|
122
|
+
path.join(testDir, '.mem', 'state.md'),
|
|
123
|
+
'---\nstatus: active\n---\n\n# State'
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const server = new MCPServer(testDir);
|
|
127
|
+
server.start();
|
|
128
|
+
|
|
129
|
+
const message = JSON.stringify({
|
|
130
|
+
jsonrpc: '2.0',
|
|
131
|
+
id: 3,
|
|
132
|
+
method: 'tools/call',
|
|
133
|
+
params: {
|
|
134
|
+
name: 'mem_status',
|
|
135
|
+
arguments: {}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
mockRlInstance.emit('line', message);
|
|
140
|
+
|
|
141
|
+
const calls = console.log.mock.calls;
|
|
142
|
+
expect(calls.length).toBeGreaterThan(0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('handles tools/call for mem_context', () => {
|
|
146
|
+
spawnSync.mockReturnValue({ status: 0, stdout: 'task/test', stderr: '' });
|
|
147
|
+
|
|
148
|
+
fs.writeFileSync(
|
|
149
|
+
path.join(testDir, '.mem', 'goal.md'),
|
|
150
|
+
'# Goal\n\nTest'
|
|
151
|
+
);
|
|
152
|
+
fs.writeFileSync(
|
|
153
|
+
path.join(testDir, '.mem', 'state.md'),
|
|
154
|
+
'---\nstatus: active\n---\n\n'
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const server = new MCPServer(testDir);
|
|
158
|
+
server.start();
|
|
159
|
+
|
|
160
|
+
const message = JSON.stringify({
|
|
161
|
+
jsonrpc: '2.0',
|
|
162
|
+
id: 4,
|
|
163
|
+
method: 'tools/call',
|
|
164
|
+
params: {
|
|
165
|
+
name: 'mem_context',
|
|
166
|
+
arguments: {}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
mockRlInstance.emit('line', message);
|
|
171
|
+
|
|
172
|
+
expect(console.log).toHaveBeenCalled();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('handles tools/call for mem_checkpoint', () => {
|
|
176
|
+
fs.writeFileSync(
|
|
177
|
+
path.join(testDir, '.mem', 'state.md'),
|
|
178
|
+
'---\nstatus: active\n---\n\n## Checkpoints\n\n- [ ] Started'
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const server = new MCPServer(testDir);
|
|
182
|
+
server.start();
|
|
183
|
+
|
|
184
|
+
const message = JSON.stringify({
|
|
185
|
+
jsonrpc: '2.0',
|
|
186
|
+
id: 5,
|
|
187
|
+
method: 'tools/call',
|
|
188
|
+
params: {
|
|
189
|
+
name: 'mem_checkpoint',
|
|
190
|
+
arguments: { text: 'New checkpoint' }
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
mockRlInstance.emit('line', message);
|
|
195
|
+
|
|
196
|
+
expect(console.log).toHaveBeenCalled();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('handles tools/call for mem_learn', () => {
|
|
200
|
+
fs.writeFileSync(
|
|
201
|
+
path.join(testDir, '.mem', 'memory.md'),
|
|
202
|
+
'# Learnings\n\n'
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const server = new MCPServer(testDir);
|
|
206
|
+
server.start();
|
|
207
|
+
|
|
208
|
+
const message = JSON.stringify({
|
|
209
|
+
jsonrpc: '2.0',
|
|
210
|
+
id: 6,
|
|
211
|
+
method: 'tools/call',
|
|
212
|
+
params: {
|
|
213
|
+
name: 'mem_learn',
|
|
214
|
+
arguments: { insight: 'New insight' }
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
mockRlInstance.emit('line', message);
|
|
219
|
+
|
|
220
|
+
expect(console.log).toHaveBeenCalled();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('handles tools/call for mem_next', () => {
|
|
224
|
+
fs.writeFileSync(
|
|
225
|
+
path.join(testDir, '.mem', 'state.md'),
|
|
226
|
+
'---\nstatus: active\n---\n\n## Next Step\n\nOld step'
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
const server = new MCPServer(testDir);
|
|
230
|
+
server.start();
|
|
231
|
+
|
|
232
|
+
const message = JSON.stringify({
|
|
233
|
+
jsonrpc: '2.0',
|
|
234
|
+
id: 7,
|
|
235
|
+
method: 'tools/call',
|
|
236
|
+
params: {
|
|
237
|
+
name: 'mem_next',
|
|
238
|
+
arguments: { step: 'New step' }
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
mockRlInstance.emit('line', message);
|
|
243
|
+
|
|
244
|
+
expect(console.log).toHaveBeenCalled();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('handles tools/call for unknown tool', () => {
|
|
248
|
+
const server = new MCPServer(testDir);
|
|
249
|
+
server.start();
|
|
250
|
+
|
|
251
|
+
const message = JSON.stringify({
|
|
252
|
+
jsonrpc: '2.0',
|
|
253
|
+
id: 8,
|
|
254
|
+
method: 'tools/call',
|
|
255
|
+
params: {
|
|
256
|
+
name: 'unknown_tool',
|
|
257
|
+
arguments: {}
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
mockRlInstance.emit('line', message);
|
|
262
|
+
|
|
263
|
+
const calls = console.log.mock.calls;
|
|
264
|
+
expect(calls.length).toBeGreaterThan(0);
|
|
265
|
+
const lastCall = calls[calls.length - 1][0];
|
|
266
|
+
const response = JSON.parse(lastCall);
|
|
267
|
+
// Unknown tool returns a result with error message in content, not a JSON-RPC error
|
|
268
|
+
expect(response.result || response.error).toBeDefined();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('handles unknown method', () => {
|
|
272
|
+
const server = new MCPServer(testDir);
|
|
273
|
+
server.start();
|
|
274
|
+
|
|
275
|
+
const message = JSON.stringify({
|
|
276
|
+
jsonrpc: '2.0',
|
|
277
|
+
id: 9,
|
|
278
|
+
method: 'unknown/method',
|
|
279
|
+
params: {}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
mockRlInstance.emit('line', message);
|
|
283
|
+
|
|
284
|
+
const calls = console.log.mock.calls;
|
|
285
|
+
const lastCall = calls[calls.length - 1][0];
|
|
286
|
+
const response = JSON.parse(lastCall);
|
|
287
|
+
expect(response.error.code).toBe(-32601);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('handles close event', () => {
|
|
291
|
+
const originalExit = process.exit;
|
|
292
|
+
process.exit = jest.fn();
|
|
293
|
+
|
|
294
|
+
const server = new MCPServer(testDir);
|
|
295
|
+
server.start();
|
|
296
|
+
|
|
297
|
+
mockRlInstance.emit('close');
|
|
298
|
+
|
|
299
|
+
expect(process.exit).toHaveBeenCalledWith(0);
|
|
300
|
+
|
|
301
|
+
process.exit = originalExit;
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('handles notification (no id)', () => {
|
|
305
|
+
const server = new MCPServer(testDir);
|
|
306
|
+
server.start();
|
|
307
|
+
|
|
308
|
+
const message = JSON.stringify({
|
|
309
|
+
jsonrpc: '2.0',
|
|
310
|
+
method: 'notifications/initialized',
|
|
311
|
+
params: {}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
mockRlInstance.emit('line', message);
|
|
315
|
+
|
|
316
|
+
// Notifications don't produce responses
|
|
317
|
+
// Just verify no error was thrown
|
|
318
|
+
expect(mockRlInstance.closed).toBe(false);
|
|
319
|
+
});
|
|
320
|
+
});
|