@goonnguyen/human-mcp 1.3.0 → 2.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/README.md +261 -19
- package/bin/human-mcp.js +2 -0
- package/dist/index.js +65180 -1698
- package/package.json +19 -2
- package/.claude/agents/code-reviewer.md +0 -140
- package/.claude/agents/database-admin.md +0 -86
- package/.claude/agents/debugger.md +0 -119
- package/.claude/agents/docs-manager.md +0 -113
- package/.claude/agents/git-manager.md +0 -59
- package/.claude/agents/planner-researcher.md +0 -97
- package/.claude/agents/project-manager.md +0 -113
- package/.claude/agents/tester.md +0 -95
- package/.claude/commands/cook.md +0 -7
- package/.claude/commands/debug.md +0 -10
- package/.claude/commands/docs/init.md +0 -11
- package/.claude/commands/docs/update.md +0 -11
- package/.claude/commands/fix/ci.md +0 -8
- package/.claude/commands/fix/fast.md +0 -5
- package/.claude/commands/fix/hard.md +0 -7
- package/.claude/commands/fix/test.md +0 -16
- package/.claude/commands/git/cm.md +0 -5
- package/.claude/commands/git/cp.md +0 -4
- package/.claude/commands/plan/ci.md +0 -12
- package/.claude/commands/plan/two.md +0 -13
- package/.claude/commands/plan.md +0 -10
- package/.claude/commands/test.md +0 -7
- package/.claude/commands/watzup.md +0 -8
- package/.claude/hooks/telegram_notify.sh +0 -136
- package/.claude/send-discord.sh +0 -64
- package/.claude/settings.json +0 -7
- package/.claude/statusline.sh +0 -143
- package/.dockerignore +0 -81
- package/.env.example +0 -44
- package/.github/workflows/publish.yml +0 -88
- package/.opencode/agent/code-reviewer.md +0 -142
- package/.opencode/agent/debugger.md +0 -74
- package/.opencode/agent/docs-manager.md +0 -119
- package/.opencode/agent/git-manager.md +0 -60
- package/.opencode/agent/planner-researcher.md +0 -100
- package/.opencode/agent/project-manager.md +0 -113
- package/.opencode/agent/system-architecture.md +0 -200
- package/.opencode/agent/tester.md +0 -96
- package/.opencode/agent/ui-ux-developer.md +0 -97
- package/.opencode/command/cook.md +0 -7
- package/.opencode/command/debug.md +0 -10
- package/.opencode/command/fix/ci.md +0 -8
- package/.opencode/command/fix/fast.md +0 -5
- package/.opencode/command/fix/hard.md +0 -7
- package/.opencode/command/fix/test.md +0 -16
- package/.opencode/command/git/cm.md +0 -5
- package/.opencode/command/git/cp.md +0 -4
- package/.opencode/command/plan/ci.md +0 -12
- package/.opencode/command/plan/two.md +0 -13
- package/.opencode/command/plan.md +0 -10
- package/.opencode/command/test.md +0 -7
- package/.opencode/command/watzup.md +0 -8
- package/.releaserc.json +0 -26
- package/.serena/project.yml +0 -68
- package/CHANGELOG.md +0 -62
- package/CLAUDE.md +0 -141
- package/DEPLOYMENT.md +0 -329
- package/Dockerfile +0 -52
- package/QUICKSTART.md +0 -97
- package/bun.lock +0 -1872
- package/bunfig.toml +0 -15
- package/docker-compose.yaml +0 -128
- package/docs/README.md +0 -51
- package/docs/codebase-structure-architecture-code-standards.md +0 -428
- package/docs/codebase-summary.md +0 -321
- package/docs/project-overview-pdr.md +0 -286
- package/docs/project-roadmap.md +0 -494
- package/examples/debugging-session.ts +0 -96
- package/human-mcp.png +0 -0
- package/inspector-wrapper.mjs +0 -33
- package/plans/001-streamable-http-transport-plan.md +0 -905
- package/plans/002-sse-fallback-http-transport-plan.md +0 -161
- package/plans/003-fix-test-infrastructure-and-ci-plan.md +0 -699
- package/plans/003-http-transport-local-file-access-plan.md +0 -880
- package/plans/004-fix-typescript-compilation-errors-plan.md +0 -388
- package/plans/005-comprehensive-test-infrastructure-fix-plan.md +0 -854
- package/plans/templates/bug-fix-template.md +0 -69
- package/plans/templates/feature-implementation-template.md +0 -84
- package/plans/templates/refactor-template.md +0 -82
- package/plans/templates/template-usage-guide.md +0 -58
- package/src/index.ts +0 -49
- package/src/prompts/debugging-prompts.ts +0 -149
- package/src/prompts/index.ts +0 -55
- package/src/resources/documentation.ts +0 -316
- package/src/resources/index.ts +0 -49
- package/src/server.ts +0 -36
- package/src/tools/eyes/index.ts +0 -225
- package/src/tools/eyes/processors/gif.ts +0 -137
- package/src/tools/eyes/processors/image.ts +0 -213
- package/src/tools/eyes/processors/video.ts +0 -135
- package/src/tools/eyes/schemas.ts +0 -51
- package/src/tools/eyes/utils/formatters.ts +0 -126
- package/src/tools/eyes/utils/gemini-client.ts +0 -73
- package/src/transports/http/file-interceptor.ts +0 -134
- package/src/transports/http/middleware.ts +0 -46
- package/src/transports/http/routes.ts +0 -297
- package/src/transports/http/server.ts +0 -116
- package/src/transports/http/session.ts +0 -93
- package/src/transports/http/sse-routes.ts +0 -210
- package/src/transports/index.ts +0 -36
- package/src/transports/stdio.ts +0 -7
- package/src/transports/types.ts +0 -50
- package/src/types/index.ts +0 -41
- package/src/utils/cloudflare-r2.ts +0 -107
- package/src/utils/config.ts +0 -123
- package/src/utils/errors.ts +0 -40
- package/src/utils/logger.ts +0 -49
- package/tests/integration/http-transport-files.test.ts +0 -190
- package/tests/integration/server.test.ts +0 -27
- package/tests/integration/sse-transport.test.ts +0 -142
- package/tests/setup.ts +0 -55
- package/tests/types/api-responses.ts +0 -35
- package/tests/types/test-types.ts +0 -105
- package/tests/unit/cloudflare-r2.test.ts +0 -118
- package/tests/unit/config.test.ts +0 -40
- package/tests/unit/eyes-analyze.test.ts +0 -150
- package/tests/unit/formatters.test.ts +0 -85
- package/tests/unit/sse-routes.test.ts +0 -92
- package/tests/utils/error-scenarios.ts +0 -198
- package/tests/utils/index.ts +0 -3
- package/tests/utils/mock-helpers.ts +0 -99
- package/tests/utils/test-data-generators.ts +0 -217
- package/tests/utils/test-server-manager.ts +0 -172
- package/tsconfig.json +0 -26
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { mock, type Mock } from 'bun:test';
|
|
2
|
-
import type { MockError, MockHttpResponseData } from '../types/test-types.js';
|
|
3
|
-
|
|
4
|
-
export interface MockedLogger {
|
|
5
|
-
info: Mock<() => void>;
|
|
6
|
-
error: Mock<() => void>;
|
|
7
|
-
warn: Mock<() => void>;
|
|
8
|
-
debug: Mock<() => void>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface MockedFS {
|
|
12
|
-
readFileSync: Mock<() => Buffer>;
|
|
13
|
-
writeFileSync: Mock<() => void>;
|
|
14
|
-
existsSync: Mock<() => boolean>;
|
|
15
|
-
mkdirSync: Mock<() => void>;
|
|
16
|
-
unlinkSync: Mock<() => void>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface MockedGeminiClient {
|
|
20
|
-
generateContent: Mock<() => Promise<any>>;
|
|
21
|
-
getGenerativeModel: Mock<() => any>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class MockHelpers {
|
|
25
|
-
static createLoggerMock(): MockedLogger {
|
|
26
|
-
return {
|
|
27
|
-
info: mock(() => {}),
|
|
28
|
-
error: mock(() => {}),
|
|
29
|
-
warn: mock(() => {}),
|
|
30
|
-
debug: mock(() => {})
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
static createFileSystemMock(): MockedFS {
|
|
35
|
-
return {
|
|
36
|
-
readFileSync: mock(() => Buffer.from('mock file content')),
|
|
37
|
-
writeFileSync: mock(() => {}),
|
|
38
|
-
existsSync: mock(() => true),
|
|
39
|
-
mkdirSync: mock(() => {}),
|
|
40
|
-
unlinkSync: mock(() => {})
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
static createGeminiClientMock(): MockedGeminiClient {
|
|
45
|
-
return {
|
|
46
|
-
generateContent: mock(() => Promise.resolve({
|
|
47
|
-
response: {
|
|
48
|
-
text: () => JSON.stringify({
|
|
49
|
-
summary: "Mock analysis result",
|
|
50
|
-
details: "Mock detailed analysis",
|
|
51
|
-
confidence: 0.95
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
})),
|
|
55
|
-
getGenerativeModel: mock(() => ({
|
|
56
|
-
generateContent: mock(() => Promise.resolve({
|
|
57
|
-
response: {
|
|
58
|
-
text: () => JSON.stringify({
|
|
59
|
-
summary: "Mock analysis result",
|
|
60
|
-
details: "Mock detailed analysis",
|
|
61
|
-
confidence: 0.95
|
|
62
|
-
})
|
|
63
|
-
}
|
|
64
|
-
}))
|
|
65
|
-
}))
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
static resetAllMocks(mocks: Record<string, unknown>): void {
|
|
70
|
-
Object.values(mocks).forEach(mockObj => {
|
|
71
|
-
if (typeof mockObj === 'object' && mockObj !== null) {
|
|
72
|
-
Object.values(mockObj).forEach(mockFn => {
|
|
73
|
-
if (typeof mockFn === 'function' && 'mockRestore' in mockFn) {
|
|
74
|
-
(mockFn as Mock<any>).mockRestore();
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
static createMockResponse(data: MockHttpResponseData, status = 200): Response {
|
|
82
|
-
return new Response(JSON.stringify(data), {
|
|
83
|
-
status,
|
|
84
|
-
headers: {
|
|
85
|
-
'Content-Type': 'application/json'
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
static createMockError(message: string, code?: string | number): MockError {
|
|
91
|
-
const error: MockError = { message };
|
|
92
|
-
if (code) {
|
|
93
|
-
error.code = code;
|
|
94
|
-
}
|
|
95
|
-
return error as MockError & Error;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export default MockHelpers;
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import type { MockAnalysisRequest, MockCompareRequest, MockGeminiResponse, MockComparisonResponse, MockHttpResponseData } from '../types/test-types.js';
|
|
2
|
-
|
|
3
|
-
export class TestDataGenerators {
|
|
4
|
-
static createBase64Image(variant: 'small' | 'medium' | 'large' = 'small'): string {
|
|
5
|
-
// Different sized images for more realistic testing
|
|
6
|
-
const images = {
|
|
7
|
-
small: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
|
|
8
|
-
medium: 'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFklEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
|
|
9
|
-
large: 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAOklEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJgggAAAABJRU5ErkJgggAAAABJRU5ErkJggg=='
|
|
10
|
-
};
|
|
11
|
-
return `data:image/png;base64,${images[variant]}`;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
static createMockImageBuffer(size: number = 1024): Buffer {
|
|
15
|
-
// Create buffer with specified size for more realistic testing
|
|
16
|
-
const base64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
|
17
|
-
const buffer = Buffer.from(base64, 'base64');
|
|
18
|
-
// Pad buffer to reach desired size
|
|
19
|
-
return size > buffer.length ? Buffer.concat([buffer, Buffer.alloc(size - buffer.length)]) : buffer;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
static createMockVideoFile(duration: number = 10): string {
|
|
23
|
-
// Mock MP4 file path with metadata
|
|
24
|
-
return `/tmp/test-video-${duration}s.mp4`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
static createMockGifFile(frames: number = 5): string {
|
|
28
|
-
// Mock GIF file path with metadata
|
|
29
|
-
return `/tmp/test-animation-${frames}frames.gif`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
static createMockAnalysisRequest(overrides: Partial<MockAnalysisRequest> = {}): MockAnalysisRequest {
|
|
33
|
-
const prompts = [
|
|
34
|
-
'Analyze the user interface elements and their layout',
|
|
35
|
-
'Focus on accessibility and usability issues',
|
|
36
|
-
'Identify any visual bugs or rendering problems',
|
|
37
|
-
'Review the overall design consistency',
|
|
38
|
-
'Check for mobile responsiveness indicators'
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
const selectedPrompt = prompts[Math.floor(Math.random() * prompts.length)];
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
input: TestDataGenerators.createBase64Image(),
|
|
45
|
-
detail_level: Math.random() > 0.5 ? 'detailed' : 'quick',
|
|
46
|
-
custom_prompt: selectedPrompt,
|
|
47
|
-
...overrides
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
static createMockCompareRequest(overrides: Partial<MockCompareRequest> = {}): MockCompareRequest {
|
|
52
|
-
const prompts = [
|
|
53
|
-
'Compare the visual differences between these two UI states',
|
|
54
|
-
'Focus on layout and structural changes',
|
|
55
|
-
'Identify pixel-level differences and their impact',
|
|
56
|
-
'Compare accessibility features between versions',
|
|
57
|
-
'Analyze the user experience implications of changes'
|
|
58
|
-
];
|
|
59
|
-
|
|
60
|
-
const comparisonTypes: Array<'pixel' | 'structural' | 'semantic'> = ['pixel', 'structural', 'semantic'];
|
|
61
|
-
const selectedPrompt = prompts[Math.floor(Math.random() * prompts.length)];
|
|
62
|
-
const selectedType = comparisonTypes[Math.floor(Math.random() * comparisonTypes.length)];
|
|
63
|
-
|
|
64
|
-
const baseRequest = {
|
|
65
|
-
input1: TestDataGenerators.createBase64Image('medium'),
|
|
66
|
-
input2: TestDataGenerators.createBase64Image('medium'),
|
|
67
|
-
comparison_type: selectedType as 'pixel' | 'structural' | 'semantic',
|
|
68
|
-
custom_prompt: selectedPrompt
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
return Object.assign({}, baseRequest, overrides) as MockCompareRequest;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
static createMockGeminiResponse(overrides: Partial<MockGeminiResponse> = {}): MockGeminiResponse {
|
|
75
|
-
const responses = [
|
|
76
|
-
{
|
|
77
|
-
summary: 'Screenshot shows a web application interface',
|
|
78
|
-
details: 'This image contains a modern web application with a navigation bar, sidebar, and main content area. The interface uses a clean design with blue accents.',
|
|
79
|
-
technical_details: {
|
|
80
|
-
dimensions: '1920x1080',
|
|
81
|
-
format: 'PNG',
|
|
82
|
-
colors: 'full color',
|
|
83
|
-
ui_elements: 'navigation, sidebar, content area'
|
|
84
|
-
},
|
|
85
|
-
confidence: 0.92,
|
|
86
|
-
recommendations: ['Consider improving color contrast', 'Add loading states for better UX']
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
summary: 'Mobile app screenshot with user interface elements',
|
|
90
|
-
details: 'This is a mobile application screenshot showing a login form with input fields and buttons. The design follows modern mobile UI patterns.',
|
|
91
|
-
technical_details: {
|
|
92
|
-
dimensions: '375x812',
|
|
93
|
-
format: 'JPEG',
|
|
94
|
-
colors: 'full color',
|
|
95
|
-
platform: 'mobile'
|
|
96
|
-
},
|
|
97
|
-
confidence: 0.88,
|
|
98
|
-
recommendations: ['Optimize for smaller screen sizes', 'Ensure touch targets are adequate']
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
summary: 'Code editor interface with syntax highlighting',
|
|
102
|
-
details: 'The image shows a code editor with syntax highlighting, line numbers, and a file tree. Multiple tabs are open showing different files.',
|
|
103
|
-
technical_details: {
|
|
104
|
-
dimensions: '1440x900',
|
|
105
|
-
format: 'PNG',
|
|
106
|
-
colors: 'dark theme',
|
|
107
|
-
editor: 'VS Code-like interface'
|
|
108
|
-
},
|
|
109
|
-
confidence: 0.96,
|
|
110
|
-
recommendations: ['Good use of syntax highlighting', 'Consider font size for readability']
|
|
111
|
-
}
|
|
112
|
-
];
|
|
113
|
-
|
|
114
|
-
const selectedResponse = responses[Math.floor(Math.random() * responses.length)];
|
|
115
|
-
return Object.assign({}, selectedResponse, overrides) as MockGeminiResponse;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
static createMockComparisonResponse(overrides: Partial<MockComparisonResponse> = {}): MockComparisonResponse {
|
|
119
|
-
const responses = [
|
|
120
|
-
{
|
|
121
|
-
summary: 'Significant UI differences detected',
|
|
122
|
-
differences: [
|
|
123
|
-
'Button color changed from blue to green',
|
|
124
|
-
'Navigation bar height increased by 10px',
|
|
125
|
-
'New search icon added in header'
|
|
126
|
-
],
|
|
127
|
-
similarity_score: 0.73,
|
|
128
|
-
analysis_method: 'semantic',
|
|
129
|
-
recommendations: [
|
|
130
|
-
'Review color accessibility standards',
|
|
131
|
-
'Test navigation changes with users',
|
|
132
|
-
'Ensure search functionality is intuitive'
|
|
133
|
-
],
|
|
134
|
-
technical_details: {
|
|
135
|
-
image1_format: 'PNG',
|
|
136
|
-
image2_format: 'PNG',
|
|
137
|
-
comparison_method: 'semantic'
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
summary: 'Minor layout adjustments found',
|
|
142
|
-
differences: [
|
|
143
|
-
'Slight margin increase in content area',
|
|
144
|
-
'Font size reduced by 1px'
|
|
145
|
-
],
|
|
146
|
-
similarity_score: 0.91,
|
|
147
|
-
analysis_method: 'structural',
|
|
148
|
-
recommendations: [
|
|
149
|
-
'Changes are minimal and unlikely to impact users',
|
|
150
|
-
'Consider A/B testing for optimal spacing'
|
|
151
|
-
],
|
|
152
|
-
technical_details: {
|
|
153
|
-
image1_format: 'JPEG',
|
|
154
|
-
image2_format: 'PNG',
|
|
155
|
-
comparison_method: 'structural'
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
summary: 'Images are nearly identical',
|
|
160
|
-
differences: [],
|
|
161
|
-
similarity_score: 0.98,
|
|
162
|
-
analysis_method: 'pixel',
|
|
163
|
-
recommendations: ['No significant changes detected'],
|
|
164
|
-
technical_details: {
|
|
165
|
-
image1_format: 'PNG',
|
|
166
|
-
image2_format: 'PNG',
|
|
167
|
-
comparison_method: 'pixel'
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
];
|
|
171
|
-
|
|
172
|
-
const selectedResponse = responses[Math.floor(Math.random() * responses.length)];
|
|
173
|
-
return Object.assign({}, selectedResponse, overrides) as MockComparisonResponse;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
static createMockFileStats() {
|
|
177
|
-
return {
|
|
178
|
-
isFile: () => true,
|
|
179
|
-
isDirectory: () => false,
|
|
180
|
-
size: 1024,
|
|
181
|
-
mtime: new Date(),
|
|
182
|
-
ctime: new Date()
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
static createMockHttpResponse(data: MockHttpResponseData, status = 200, headers: Record<string, string> = {}) {
|
|
187
|
-
return new Response(typeof data === 'string' ? data : JSON.stringify(data), {
|
|
188
|
-
status,
|
|
189
|
-
headers: {
|
|
190
|
-
'Content-Type': 'application/json',
|
|
191
|
-
...headers
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
static createMockErrorResponse(message: string, status = 500) {
|
|
197
|
-
return new Response(JSON.stringify({ error: message }), {
|
|
198
|
-
status,
|
|
199
|
-
headers: { 'Content-Type': 'application/json' }
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
static generateRandomPort(): number {
|
|
204
|
-
return 3000 + Math.floor(Math.random() * 1000);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
static createMockSessionData() {
|
|
208
|
-
return {
|
|
209
|
-
id: 'test-session-123',
|
|
210
|
-
created: Date.now(),
|
|
211
|
-
lastActivity: Date.now(),
|
|
212
|
-
data: {}
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export default TestDataGenerators;
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { startHttpTransport } from "../../src/transports/http/server.js";
|
|
3
|
-
import type { HttpTransportConfig, HttpServerHandle } from "../../src/transports/types.js";
|
|
4
|
-
|
|
5
|
-
export class TestServerManager {
|
|
6
|
-
private servers: Map<number, HttpServerHandle> = new Map();
|
|
7
|
-
private usedPorts: Set<number> = new Set();
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Get a random available port for testing using OS-level port checking
|
|
11
|
-
*/
|
|
12
|
-
async getAvailablePort(): Promise<number> {
|
|
13
|
-
// Try sequential ports starting from a random high port
|
|
14
|
-
const basePort = 4000 + Math.floor(Math.random() * 1000);
|
|
15
|
-
const maxAttempts = 50;
|
|
16
|
-
|
|
17
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
18
|
-
const port = basePort + attempt;
|
|
19
|
-
|
|
20
|
-
if (!this.usedPorts.has(port) && await this.isPortAvailable(port)) {
|
|
21
|
-
this.usedPorts.add(port);
|
|
22
|
-
return port;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
throw new Error(`Unable to find available port after ${maxAttempts} attempts (tried ${basePort} to ${basePort + maxAttempts - 1})`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Check if a port is available using Node.js net module
|
|
31
|
-
*/
|
|
32
|
-
private async isPortAvailable(port: number): Promise<boolean> {
|
|
33
|
-
return new Promise((resolve) => {
|
|
34
|
-
const net = require('net');
|
|
35
|
-
const server = net.createServer();
|
|
36
|
-
|
|
37
|
-
server.listen(port, '127.0.0.1', () => {
|
|
38
|
-
server.close(() => {
|
|
39
|
-
resolve(true);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
server.on('error', () => {
|
|
44
|
-
resolve(false);
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Start a test server with the given configuration
|
|
51
|
-
*/
|
|
52
|
-
async startTestServer(config: Partial<HttpTransportConfig> = {}): Promise<{
|
|
53
|
-
server: HttpServerHandle;
|
|
54
|
-
port: number;
|
|
55
|
-
baseUrl: string;
|
|
56
|
-
}> {
|
|
57
|
-
const port = await this.getAvailablePort();
|
|
58
|
-
|
|
59
|
-
// Create a basic MCP server for testing
|
|
60
|
-
const mcpServer = new McpServer(
|
|
61
|
-
{
|
|
62
|
-
name: "test-server",
|
|
63
|
-
version: "1.0.0"
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
capabilities: {
|
|
67
|
-
tools: {}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
const serverConfig: HttpTransportConfig = {
|
|
73
|
-
port,
|
|
74
|
-
host: "127.0.0.1",
|
|
75
|
-
sessionMode: "stateful",
|
|
76
|
-
enableSse: true,
|
|
77
|
-
enableJsonResponse: true,
|
|
78
|
-
enableSseFallback: true,
|
|
79
|
-
ssePaths: {
|
|
80
|
-
stream: "/sse",
|
|
81
|
-
message: "/messages"
|
|
82
|
-
},
|
|
83
|
-
security: {
|
|
84
|
-
enableCors: true,
|
|
85
|
-
enableDnsRebindingProtection: true,
|
|
86
|
-
allowedHosts: ["127.0.0.1", "localhost"]
|
|
87
|
-
},
|
|
88
|
-
...config
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const server = await startHttpTransport(mcpServer, serverConfig);
|
|
92
|
-
this.servers.set(port, server);
|
|
93
|
-
|
|
94
|
-
// Wait for server to be ready
|
|
95
|
-
await this.waitForServerReady(port);
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
server,
|
|
99
|
-
port,
|
|
100
|
-
baseUrl: `http://127.0.0.1:${port}`
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Wait for server to be ready by checking health endpoint
|
|
106
|
-
*/
|
|
107
|
-
private async waitForServerReady(port: number, timeout = 15000): Promise<void> {
|
|
108
|
-
const startTime = Date.now();
|
|
109
|
-
let lastError: Error | undefined;
|
|
110
|
-
|
|
111
|
-
while (Date.now() - startTime < timeout) {
|
|
112
|
-
try {
|
|
113
|
-
const response = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
114
|
-
signal: AbortSignal.timeout(2000)
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
if (response.ok) {
|
|
118
|
-
const health = await response.json() as { status: string };
|
|
119
|
-
if (health.status === 'healthy') {
|
|
120
|
-
// Give the server a bit more time to fully initialize
|
|
121
|
-
await new Promise(resolve => setTimeout(resolve, 200));
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
} catch (error) {
|
|
126
|
-
lastError = error as Error;
|
|
127
|
-
// Server not ready yet, continue waiting
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
await new Promise(resolve => setTimeout(resolve, 250));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
throw new Error(`Server on port ${port} did not become ready within ${timeout}ms. Last error: ${lastError?.message || 'Unknown'}`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Stop a specific server by port
|
|
138
|
-
*/
|
|
139
|
-
async stopServer(port: number): Promise<void> {
|
|
140
|
-
const server = this.servers.get(port);
|
|
141
|
-
if (server) {
|
|
142
|
-
await server.close();
|
|
143
|
-
this.servers.delete(port);
|
|
144
|
-
this.usedPorts.delete(port);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Stop all test servers
|
|
150
|
-
*/
|
|
151
|
-
async stopAllServers(): Promise<void> {
|
|
152
|
-
const stopPromises = Array.from(this.servers.keys()).map(port => this.stopServer(port));
|
|
153
|
-
await Promise.all(stopPromises);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Get the number of active servers
|
|
158
|
-
*/
|
|
159
|
-
getActiveServerCount(): number {
|
|
160
|
-
return this.servers.size;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Check if any servers are still running
|
|
165
|
-
*/
|
|
166
|
-
hasActiveServers(): boolean {
|
|
167
|
-
return this.servers.size > 0;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Global test server manager instance
|
|
172
|
-
export const testServerManager = new TestServerManager();
|
package/tsconfig.json
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"lib": ["ESNext"],
|
|
4
|
-
"target": "ESNext",
|
|
5
|
-
"module": "ESNext",
|
|
6
|
-
"moduleResolution": "bundler",
|
|
7
|
-
"jsx": "react-jsx",
|
|
8
|
-
"allowJs": true,
|
|
9
|
-
"allowImportingTsExtensions": true,
|
|
10
|
-
"verbatimModuleSyntax": true,
|
|
11
|
-
"noEmit": true,
|
|
12
|
-
"strict": true,
|
|
13
|
-
"skipLibCheck": true,
|
|
14
|
-
"noFallthroughCasesInSwitch": true,
|
|
15
|
-
"noUncheckedIndexedAccess": true,
|
|
16
|
-
"noImplicitOverride": true,
|
|
17
|
-
"esModuleInterop": true,
|
|
18
|
-
"resolveJsonModule": true,
|
|
19
|
-
"baseUrl": "./src",
|
|
20
|
-
"paths": {
|
|
21
|
-
"@/*": ["*"]
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
"include": ["src/**/*", "tests/**/*"],
|
|
25
|
-
"exclude": ["node_modules", "dist"]
|
|
26
|
-
}
|