@hanzo/dev 2.1.1 → 3.0.2
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 +278 -279
- package/bin/dev.js +413 -0
- package/package.json +32 -61
- package/postinstall.js +513 -0
- package/scripts/preinstall.js +69 -0
- package/scripts/windows-cleanup.ps1 +31 -0
- package/.eslintrc.json +0 -24
- package/dist/cli/dev.js +0 -24746
- package/src/cli/dev.ts +0 -946
- package/src/lib/agent-loop.ts +0 -552
- package/src/lib/benchmark-runner.ts +0 -431
- package/src/lib/code-act-agent.ts +0 -378
- package/src/lib/config.ts +0 -163
- package/src/lib/editor.ts +0 -395
- package/src/lib/function-calling.ts +0 -318
- package/src/lib/mcp-client.ts +0 -259
- package/src/lib/peer-agent-network.ts +0 -584
- package/src/lib/swarm-runner.ts +0 -389
- package/src/lib/unified-workspace.ts +0 -435
- package/test-swarm/file1.js +0 -6
- package/test-swarm/file2.ts +0 -12
- package/test-swarm/file3.py +0 -15
- package/test-swarm/file4.md +0 -13
- package/test-swarm/file5.json +0 -12
- package/test-swarm-demo.sh +0 -22
- package/tests/browser-integration.test.ts +0 -242
- package/tests/code-act-agent.test.ts +0 -305
- package/tests/editor.test.ts +0 -223
- package/tests/fixtures/sample-code.js +0 -13
- package/tests/fixtures/sample-code.py +0 -28
- package/tests/fixtures/sample-code.ts +0 -22
- package/tests/mcp-client.test.ts +0 -238
- package/tests/peer-agent-network.test.ts +0 -340
- package/tests/swarm-runner.test.ts +0 -301
- package/tests/swe-bench.test.ts +0 -357
- package/tsconfig.cli.json +0 -25
- package/tsconfig.json +0 -35
- package/vitest.config.ts +0 -37
package/tests/editor.test.ts
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import * as os from 'os';
|
|
5
|
-
import { FileEditor, EditCommand } from '../src/lib/editor';
|
|
6
|
-
|
|
7
|
-
describe('Editor', () => {
|
|
8
|
-
let editor: FileEditor;
|
|
9
|
-
let testDir: string;
|
|
10
|
-
let testFile: string;
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
// Create temporary test directory
|
|
14
|
-
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hanzo-dev-test-'));
|
|
15
|
-
testFile = path.join(testDir, 'test.txt');
|
|
16
|
-
editor = new FileEditor(testDir);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
// Clean up test directory
|
|
21
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe('create command', () => {
|
|
25
|
-
test('should create a new file with content', async () => {
|
|
26
|
-
const command: EditCommand = {
|
|
27
|
-
command: 'create',
|
|
28
|
-
path: testFile,
|
|
29
|
-
content: 'Hello, World!'
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const result = await editor.execute(command);
|
|
33
|
-
expect(result.success).toBe(true);
|
|
34
|
-
expect(fs.existsSync(testFile)).toBe(true);
|
|
35
|
-
expect(fs.readFileSync(testFile, 'utf-8')).toBe('Hello, World!');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('should fail when file already exists', async () => {
|
|
39
|
-
fs.writeFileSync(testFile, 'existing content');
|
|
40
|
-
|
|
41
|
-
const command: EditCommand = {
|
|
42
|
-
command: 'create',
|
|
43
|
-
path: testFile,
|
|
44
|
-
content: 'new content'
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const result = await editor.execute(command);
|
|
48
|
-
expect(result.success).toBe(false);
|
|
49
|
-
expect(result.error).toBe('FILE_EXISTS');
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe('view command', () => {
|
|
54
|
-
test('should view entire file when no line range specified', async () => {
|
|
55
|
-
const content = 'Line 1\nLine 2\nLine 3';
|
|
56
|
-
fs.writeFileSync(testFile, content);
|
|
57
|
-
|
|
58
|
-
const command: EditCommand = {
|
|
59
|
-
command: 'view',
|
|
60
|
-
path: testFile
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const result = await editor.execute(command);
|
|
64
|
-
expect(result.success).toBe(true);
|
|
65
|
-
expect(result.content).toContain('Line 1');
|
|
66
|
-
expect(result.content).toContain('Line 2');
|
|
67
|
-
expect(result.content).toContain('Line 3');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('should view specific line range', async () => {
|
|
71
|
-
const lines = Array.from({ length: 10 }, (_, i) => `Line ${i + 1}`);
|
|
72
|
-
fs.writeFileSync(testFile, lines.join('\n'));
|
|
73
|
-
|
|
74
|
-
const command: EditCommand = {
|
|
75
|
-
command: 'view',
|
|
76
|
-
path: testFile,
|
|
77
|
-
startLine: 3,
|
|
78
|
-
endLine: 5
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const result = await editor.execute(command);
|
|
82
|
-
expect(result.success).toBe(true);
|
|
83
|
-
expect(result.content).toContain('Line 3');
|
|
84
|
-
expect(result.content).toContain('Line 4');
|
|
85
|
-
expect(result.content).toContain('Line 5');
|
|
86
|
-
expect(result.content).not.toContain('Line 1');
|
|
87
|
-
expect(result.content).not.toContain('Line 10');
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
describe('str_replace command', () => {
|
|
92
|
-
test('should replace string in file', async () => {
|
|
93
|
-
const content = 'Hello, World!\nThis is a test.\nHello again!';
|
|
94
|
-
fs.writeFileSync(testFile, content);
|
|
95
|
-
|
|
96
|
-
const command: EditCommand = {
|
|
97
|
-
command: 'str_replace',
|
|
98
|
-
path: testFile,
|
|
99
|
-
oldStr: 'Hello, World!',
|
|
100
|
-
newStr: 'Hi, Universe!'
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const result = await editor.execute(command);
|
|
104
|
-
expect(result.success).toBe(true);
|
|
105
|
-
|
|
106
|
-
const newContent = fs.readFileSync(testFile, 'utf-8');
|
|
107
|
-
expect(newContent).toContain('Hi, Universe!');
|
|
108
|
-
expect(newContent).not.toContain('Hello, World!');
|
|
109
|
-
expect(newContent).toContain('Hello again!');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('should fail when old string not found', async () => {
|
|
113
|
-
fs.writeFileSync(testFile, 'Some content');
|
|
114
|
-
|
|
115
|
-
const command: EditCommand = {
|
|
116
|
-
command: 'str_replace',
|
|
117
|
-
path: testFile,
|
|
118
|
-
oldStr: 'Not found',
|
|
119
|
-
newStr: 'Replacement'
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const result = await editor.execute(command);
|
|
123
|
-
expect(result.success).toBe(false);
|
|
124
|
-
expect(result.error).toBe('STRING_NOT_FOUND');
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test('should fail when old string appears multiple times', async () => {
|
|
128
|
-
const content = 'duplicate\nsome text\nduplicate';
|
|
129
|
-
fs.writeFileSync(testFile, content);
|
|
130
|
-
|
|
131
|
-
const command: EditCommand = {
|
|
132
|
-
command: 'str_replace',
|
|
133
|
-
path: testFile,
|
|
134
|
-
oldStr: 'duplicate',
|
|
135
|
-
newStr: 'unique'
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const result = await editor.execute(command);
|
|
139
|
-
expect(result.success).toBe(false);
|
|
140
|
-
expect(result.error).toBe('STRING_NOT_UNIQUE');
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe('insert command', () => {
|
|
145
|
-
test('should insert text at specific line', async () => {
|
|
146
|
-
const content = 'Line 1\nLine 2\nLine 3';
|
|
147
|
-
fs.writeFileSync(testFile, content);
|
|
148
|
-
|
|
149
|
-
const command: EditCommand = {
|
|
150
|
-
command: 'insert',
|
|
151
|
-
path: testFile,
|
|
152
|
-
lineNumber: 2,
|
|
153
|
-
content: 'Inserted line'
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const result = await editor.execute(command);
|
|
157
|
-
expect(result.success).toBe(true);
|
|
158
|
-
|
|
159
|
-
const newContent = fs.readFileSync(testFile, 'utf-8');
|
|
160
|
-
const lines = newContent.split('\n');
|
|
161
|
-
expect(lines[1]).toBe('Inserted line');
|
|
162
|
-
expect(lines[2]).toBe('Line 2');
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
describe('undo_edit command', () => {
|
|
167
|
-
test('should undo last edit', async () => {
|
|
168
|
-
const originalContent = 'Original content';
|
|
169
|
-
fs.writeFileSync(testFile, originalContent);
|
|
170
|
-
|
|
171
|
-
// Make an edit
|
|
172
|
-
await editor.execute({
|
|
173
|
-
command: 'str_replace',
|
|
174
|
-
path: testFile,
|
|
175
|
-
oldStr: 'Original',
|
|
176
|
-
newStr: 'Modified'
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// Verify edit was made
|
|
180
|
-
expect(fs.readFileSync(testFile, 'utf-8')).toContain('Modified');
|
|
181
|
-
|
|
182
|
-
// Undo the edit
|
|
183
|
-
const result = await editor.execute({
|
|
184
|
-
command: 'undo_edit',
|
|
185
|
-
path: testFile
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
expect(result.success).toBe(true);
|
|
189
|
-
expect(fs.readFileSync(testFile, 'utf-8')).toBe(originalContent);
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
describe('chunk localization', () => {
|
|
194
|
-
test('should find relevant chunks for search query', async () => {
|
|
195
|
-
const codeContent = `
|
|
196
|
-
function calculateTotal(items) {
|
|
197
|
-
let total = 0;
|
|
198
|
-
for (const item of items) {
|
|
199
|
-
total += item.price * item.quantity;
|
|
200
|
-
}
|
|
201
|
-
return total;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function applyDiscount(total, discountPercent) {
|
|
205
|
-
return total * (1 - discountPercent / 100);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function formatCurrency(amount) {
|
|
209
|
-
return new Intl.NumberFormat('en-US', {
|
|
210
|
-
style: 'currency',
|
|
211
|
-
currency: 'USD'
|
|
212
|
-
}).format(amount);
|
|
213
|
-
}
|
|
214
|
-
`;
|
|
215
|
-
fs.writeFileSync(testFile, codeContent);
|
|
216
|
-
|
|
217
|
-
const chunks = await editor.getRelevantChunks(testFile, 'calculate price total');
|
|
218
|
-
expect(chunks.length).toBeGreaterThan(0);
|
|
219
|
-
expect(chunks[0].content).toContain('calculateTotal');
|
|
220
|
-
expect(chunks[0].content).toContain('price');
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# Sample Python code for testing
|
|
2
|
-
import json
|
|
3
|
-
from typing import List, Dict, Optional
|
|
4
|
-
|
|
5
|
-
class DataProcessor:
|
|
6
|
-
"""Process and analyze data."""
|
|
7
|
-
|
|
8
|
-
def __init__(self):
|
|
9
|
-
self.data = []
|
|
10
|
-
|
|
11
|
-
def add_item(self, item: Dict) -> None:
|
|
12
|
-
"""Add an item to the dataset."""
|
|
13
|
-
self.data.append(item)
|
|
14
|
-
|
|
15
|
-
def get_summary(self) -> Dict:
|
|
16
|
-
"""Get summary statistics."""
|
|
17
|
-
if not self.data:
|
|
18
|
-
return {"count": 0, "total": 0, "average": 0}
|
|
19
|
-
|
|
20
|
-
values = [item.get("value", 0) for item in self.data]
|
|
21
|
-
total = sum(values)
|
|
22
|
-
count = len(values)
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
"count": count,
|
|
26
|
-
"total": total,
|
|
27
|
-
"average": total / count if count > 0 else 0
|
|
28
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
// Sample TypeScript code for testing
|
|
2
|
-
interface User {
|
|
3
|
-
id: number;
|
|
4
|
-
name: string;
|
|
5
|
-
email: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export class UserService {
|
|
9
|
-
private users: User[] = [];
|
|
10
|
-
|
|
11
|
-
addUser(user: User): void {
|
|
12
|
-
this.users.push(user);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
getUser(id: number): User | undefined {
|
|
16
|
-
return this.users.find(user => user.id === id);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
getAllUsers(): User[] {
|
|
20
|
-
return [...this.users];
|
|
21
|
-
}
|
|
22
|
-
}
|
package/tests/mcp-client.test.ts
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { MCPClient, MCPSession, MCPServerConfig } from '../src/lib/mcp-client';
|
|
3
|
-
import { EventEmitter } from 'events';
|
|
4
|
-
import * as child_process from 'child_process';
|
|
5
|
-
|
|
6
|
-
// Mock child_process
|
|
7
|
-
vi.mock('child_process');
|
|
8
|
-
|
|
9
|
-
describe('MCPClient', () => {
|
|
10
|
-
let client: MCPClient;
|
|
11
|
-
let mockProcess: any;
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
client = new MCPClient();
|
|
15
|
-
|
|
16
|
-
// Mock spawn to return a fake process
|
|
17
|
-
mockProcess = new EventEmitter();
|
|
18
|
-
mockProcess.stdin = { write: vi.fn() };
|
|
19
|
-
mockProcess.stdout = new EventEmitter();
|
|
20
|
-
mockProcess.stderr = new EventEmitter();
|
|
21
|
-
mockProcess.kill = vi.fn();
|
|
22
|
-
|
|
23
|
-
vi.mocked(child_process.spawn).mockReturnValue(mockProcess as any);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
afterEach(() => {
|
|
27
|
-
vi.clearAllMocks();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe('stdio transport', () => {
|
|
31
|
-
test('should connect to MCP server via stdio', async () => {
|
|
32
|
-
const config: MCPServerConfig = {
|
|
33
|
-
name: 'test-server',
|
|
34
|
-
transport: 'stdio',
|
|
35
|
-
command: 'test-mcp-server',
|
|
36
|
-
args: ['--test']
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// Start connection in background
|
|
40
|
-
const connectPromise = client.connect(config);
|
|
41
|
-
|
|
42
|
-
// Simulate server sending initialization response
|
|
43
|
-
setTimeout(() => {
|
|
44
|
-
mockProcess.stdout.emit('data', JSON.stringify({
|
|
45
|
-
jsonrpc: '2.0',
|
|
46
|
-
id: 1,
|
|
47
|
-
result: {
|
|
48
|
-
protocolVersion: '1.0',
|
|
49
|
-
serverInfo: { name: 'test-server', version: '1.0.0' }
|
|
50
|
-
}
|
|
51
|
-
}) + '\n');
|
|
52
|
-
|
|
53
|
-
// Simulate tools list response
|
|
54
|
-
mockProcess.stdout.emit('data', JSON.stringify({
|
|
55
|
-
jsonrpc: '2.0',
|
|
56
|
-
id: 2,
|
|
57
|
-
result: {
|
|
58
|
-
tools: [
|
|
59
|
-
{
|
|
60
|
-
name: 'test_tool',
|
|
61
|
-
description: 'A test tool',
|
|
62
|
-
parameters: {
|
|
63
|
-
type: 'object',
|
|
64
|
-
properties: {
|
|
65
|
-
input: { type: 'string' }
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
]
|
|
70
|
-
}
|
|
71
|
-
}) + '\n');
|
|
72
|
-
}, 10);
|
|
73
|
-
|
|
74
|
-
const session = await connectPromise;
|
|
75
|
-
expect(session).toBeDefined();
|
|
76
|
-
expect(session.tools).toHaveLength(1);
|
|
77
|
-
expect(session.tools[0].name).toBe('test_tool');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('should handle server errors', async () => {
|
|
81
|
-
const config: MCPServerConfig = {
|
|
82
|
-
name: 'error-server',
|
|
83
|
-
transport: 'stdio',
|
|
84
|
-
command: 'failing-server'
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const connectPromise = client.connect(config);
|
|
88
|
-
|
|
89
|
-
// Simulate process error
|
|
90
|
-
setTimeout(() => {
|
|
91
|
-
mockProcess.emit('error', new Error('Failed to start'));
|
|
92
|
-
}, 10);
|
|
93
|
-
|
|
94
|
-
await expect(connectPromise).rejects.toThrow('Failed to start');
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
describe('tool calling', () => {
|
|
99
|
-
test('should call tool on MCP server', async () => {
|
|
100
|
-
const session: MCPSession = {
|
|
101
|
-
serverName: 'test-server',
|
|
102
|
-
tools: [{
|
|
103
|
-
name: 'echo',
|
|
104
|
-
description: 'Echo input',
|
|
105
|
-
parameters: {
|
|
106
|
-
type: 'object',
|
|
107
|
-
properties: {
|
|
108
|
-
message: { type: 'string' }
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}],
|
|
112
|
-
prompts: [],
|
|
113
|
-
resources: []
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
// Mock session in client
|
|
117
|
-
(client as any).sessions.set('test-server', session);
|
|
118
|
-
(client as any).processes.set('test-server', mockProcess);
|
|
119
|
-
|
|
120
|
-
// Start tool call
|
|
121
|
-
const callPromise = client.callTool('test-server', 'echo', { message: 'Hello' });
|
|
122
|
-
|
|
123
|
-
// Simulate server response
|
|
124
|
-
setTimeout(() => {
|
|
125
|
-
// Find the request that was sent
|
|
126
|
-
const writeCall = mockProcess.stdin.write.mock.calls[0];
|
|
127
|
-
const request = JSON.parse(writeCall[0]);
|
|
128
|
-
|
|
129
|
-
// Send response with same ID
|
|
130
|
-
mockProcess.stdout.emit('data', JSON.stringify({
|
|
131
|
-
jsonrpc: '2.0',
|
|
132
|
-
id: request.id,
|
|
133
|
-
result: {
|
|
134
|
-
output: 'Echo: Hello'
|
|
135
|
-
}
|
|
136
|
-
}) + '\n');
|
|
137
|
-
}, 10);
|
|
138
|
-
|
|
139
|
-
const result = await callPromise;
|
|
140
|
-
expect(result.output).toBe('Echo: Hello');
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe('session management', () => {
|
|
145
|
-
test('should list connected sessions', async () => {
|
|
146
|
-
// Mock two sessions
|
|
147
|
-
(client as any).sessions.set('server1', {
|
|
148
|
-
serverName: 'server1',
|
|
149
|
-
tools: [],
|
|
150
|
-
prompts: [],
|
|
151
|
-
resources: []
|
|
152
|
-
});
|
|
153
|
-
(client as any).sessions.set('server2', {
|
|
154
|
-
serverName: 'server2',
|
|
155
|
-
tools: [],
|
|
156
|
-
prompts: [],
|
|
157
|
-
resources: []
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
const sessions = client.listSessions();
|
|
161
|
-
expect(sessions).toHaveLength(2);
|
|
162
|
-
expect(sessions.map(s => s.serverName)).toContain('server1');
|
|
163
|
-
expect(sessions.map(s => s.serverName)).toContain('server2');
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test('should disconnect from server', async () => {
|
|
167
|
-
const serverName = 'test-server';
|
|
168
|
-
|
|
169
|
-
// Mock session and process
|
|
170
|
-
(client as any).sessions.set(serverName, {
|
|
171
|
-
serverName,
|
|
172
|
-
tools: [],
|
|
173
|
-
prompts: [],
|
|
174
|
-
resources: []
|
|
175
|
-
});
|
|
176
|
-
(client as any).processes.set(serverName, mockProcess);
|
|
177
|
-
|
|
178
|
-
await client.disconnect(serverName);
|
|
179
|
-
|
|
180
|
-
expect(mockProcess.kill).toHaveBeenCalled();
|
|
181
|
-
expect(client.listSessions()).toHaveLength(0);
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
describe('error handling', () => {
|
|
186
|
-
test('should handle JSON-RPC errors', async () => {
|
|
187
|
-
const session: MCPSession = {
|
|
188
|
-
serverName: 'test-server',
|
|
189
|
-
tools: [{
|
|
190
|
-
name: 'failing_tool',
|
|
191
|
-
description: 'A tool that fails',
|
|
192
|
-
parameters: { type: 'object' }
|
|
193
|
-
}],
|
|
194
|
-
prompts: [],
|
|
195
|
-
resources: []
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
(client as any).sessions.set('test-server', session);
|
|
199
|
-
(client as any).processes.set('test-server', mockProcess);
|
|
200
|
-
|
|
201
|
-
const callPromise = client.callTool('test-server', 'failing_tool', {});
|
|
202
|
-
|
|
203
|
-
setTimeout(() => {
|
|
204
|
-
const writeCall = mockProcess.stdin.write.mock.calls[0];
|
|
205
|
-
const request = JSON.parse(writeCall[0]);
|
|
206
|
-
|
|
207
|
-
// Send error response
|
|
208
|
-
mockProcess.stdout.emit('data', JSON.stringify({
|
|
209
|
-
jsonrpc: '2.0',
|
|
210
|
-
id: request.id,
|
|
211
|
-
error: {
|
|
212
|
-
code: -32601,
|
|
213
|
-
message: 'Method not found'
|
|
214
|
-
}
|
|
215
|
-
}) + '\n');
|
|
216
|
-
}, 10);
|
|
217
|
-
|
|
218
|
-
await expect(callPromise).rejects.toThrow('Method not found');
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
test('should handle malformed responses', async () => {
|
|
222
|
-
const config: MCPServerConfig = {
|
|
223
|
-
name: 'malformed-server',
|
|
224
|
-
transport: 'stdio',
|
|
225
|
-
command: 'test-server'
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
const connectPromise = client.connect(config);
|
|
229
|
-
|
|
230
|
-
setTimeout(() => {
|
|
231
|
-
// Send malformed JSON
|
|
232
|
-
mockProcess.stdout.emit('data', 'not valid json\n');
|
|
233
|
-
}, 10);
|
|
234
|
-
|
|
235
|
-
await expect(connectPromise).rejects.toThrow();
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
});
|