@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
package/src/utils/errors.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
export class HumanMCPError extends Error {
|
|
2
|
-
constructor(
|
|
3
|
-
message: string,
|
|
4
|
-
public code: string,
|
|
5
|
-
public statusCode?: number
|
|
6
|
-
) {
|
|
7
|
-
super(message);
|
|
8
|
-
this.name = "HumanMCPError";
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class ValidationError extends HumanMCPError {
|
|
13
|
-
constructor(message: string) {
|
|
14
|
-
super(message, "VALIDATION_ERROR", 400);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export class ProcessingError extends HumanMCPError {
|
|
19
|
-
constructor(message: string) {
|
|
20
|
-
super(message, "PROCESSING_ERROR", 500);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class APIError extends HumanMCPError {
|
|
25
|
-
constructor(message: string, statusCode: number = 500) {
|
|
26
|
-
super(message, "API_ERROR", statusCode);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function handleError(error: unknown): HumanMCPError {
|
|
31
|
-
if (error instanceof HumanMCPError) {
|
|
32
|
-
return error;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (error instanceof Error) {
|
|
36
|
-
return new ProcessingError(error.message);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return new ProcessingError("An unknown error occurred");
|
|
40
|
-
}
|
package/src/utils/logger.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { LogLevel } from "@/types";
|
|
2
|
-
|
|
3
|
-
class Logger {
|
|
4
|
-
private level: LogLevel;
|
|
5
|
-
|
|
6
|
-
constructor() {
|
|
7
|
-
this.level = (process.env.LOG_LEVEL as LogLevel) || "info";
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
private shouldLog(level: LogLevel): boolean {
|
|
11
|
-
const levels: LogLevel[] = ["debug", "info", "warn", "error"];
|
|
12
|
-
return levels.indexOf(level) >= levels.indexOf(this.level);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
private format(level: LogLevel, message: string, ...args: any[]): string {
|
|
16
|
-
const timestamp = new Date().toISOString();
|
|
17
|
-
const formatted = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
|
|
18
|
-
if (args.length > 0) {
|
|
19
|
-
return `${formatted} ${JSON.stringify(args)}`;
|
|
20
|
-
}
|
|
21
|
-
return formatted;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
debug(message: string, ...args: any[]) {
|
|
25
|
-
if (this.shouldLog("debug")) {
|
|
26
|
-
console.error(this.format("debug", message, ...args));
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
info(message: string, ...args: any[]) {
|
|
31
|
-
if (this.shouldLog("info")) {
|
|
32
|
-
console.error(this.format("info", message, ...args));
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
warn(message: string, ...args: any[]) {
|
|
37
|
-
if (this.shouldLog("warn")) {
|
|
38
|
-
console.error(this.format("warn", message, ...args));
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
error(message: string, ...args: any[]) {
|
|
43
|
-
if (this.shouldLog("error")) {
|
|
44
|
-
console.error(this.format("error", message, ...args));
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export const logger = new Logger();
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll, mock } from 'bun:test';
|
|
2
|
-
import { fileInterceptorMiddleware } from '@/transports/http/file-interceptor';
|
|
3
|
-
import type { Request, Response, NextFunction } from 'express';
|
|
4
|
-
|
|
5
|
-
// Mock the Cloudflare R2 client
|
|
6
|
-
mock.module('@/utils/cloudflare-r2', () => ({
|
|
7
|
-
CloudflareR2Client: mock(function() {
|
|
8
|
-
return {
|
|
9
|
-
uploadFile: mock(async (_buffer: Buffer, filename: string) => {
|
|
10
|
-
return `https://cdn.test.com/human-mcp/${filename}`;
|
|
11
|
-
}),
|
|
12
|
-
uploadBase64: mock(async (_data: string, _mimeType: string, filename?: string) => {
|
|
13
|
-
return `https://cdn.test.com/human-mcp/${filename || 'upload.jpg'}`;
|
|
14
|
-
}),
|
|
15
|
-
isConfigured: mock(() => true)
|
|
16
|
-
};
|
|
17
|
-
}),
|
|
18
|
-
getCloudflareR2: mock(() => ({
|
|
19
|
-
uploadFile: mock(async (_buffer: Buffer, filename: string) => {
|
|
20
|
-
return `https://cdn.test.com/human-mcp/${filename}`;
|
|
21
|
-
}),
|
|
22
|
-
uploadBase64: mock(async (_data: string, _mimeType: string, filename?: string) => {
|
|
23
|
-
return `https://cdn.test.com/human-mcp/${filename || 'upload.jpg'}`;
|
|
24
|
-
}),
|
|
25
|
-
isConfigured: mock(() => true)
|
|
26
|
-
}))
|
|
27
|
-
}));
|
|
28
|
-
|
|
29
|
-
// Logger is mocked globally in setup.ts
|
|
30
|
-
|
|
31
|
-
// Mock fs/promises module for Bun compatibility
|
|
32
|
-
mock.module('fs/promises', () => ({
|
|
33
|
-
access: mock(async () => { throw new Error('File not found'); }),
|
|
34
|
-
readFile: mock(async () => Buffer.from('fake image data')),
|
|
35
|
-
stat: mock(async () => ({ isFile: () => true }))
|
|
36
|
-
}));
|
|
37
|
-
|
|
38
|
-
describe('HTTP Transport File Handling', () => {
|
|
39
|
-
beforeAll(() => {
|
|
40
|
-
process.env.TRANSPORT_TYPE = 'http';
|
|
41
|
-
// Set required Cloudflare R2 environment variables for testing
|
|
42
|
-
process.env.CLOUDFLARE_CDN_ACCESS_KEY = 'test-access-key';
|
|
43
|
-
process.env.CLOUDFLARE_CDN_SECRET_KEY = 'test-secret-key';
|
|
44
|
-
process.env.CLOUDFLARE_CDN_ENDPOINT_URL = 'https://test.r2.cloudflarestorage.com';
|
|
45
|
-
process.env.CLOUDFLARE_CDN_BUCKET_NAME = 'test-bucket';
|
|
46
|
-
process.env.CLOUDFLARE_CDN_BASE_URL = 'https://cdn.test.com';
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
afterAll(() => {
|
|
50
|
-
delete process.env.TRANSPORT_TYPE;
|
|
51
|
-
delete process.env.CLOUDFLARE_CDN_ACCESS_KEY;
|
|
52
|
-
delete process.env.CLOUDFLARE_CDN_SECRET_KEY;
|
|
53
|
-
delete process.env.CLOUDFLARE_CDN_ENDPOINT_URL;
|
|
54
|
-
delete process.env.CLOUDFLARE_CDN_BUCKET_NAME;
|
|
55
|
-
delete process.env.CLOUDFLARE_CDN_BASE_URL;
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('should handle Claude Desktop virtual paths', async () => {
|
|
59
|
-
const req = {
|
|
60
|
-
body: {
|
|
61
|
-
method: 'tools/call',
|
|
62
|
-
params: {
|
|
63
|
-
arguments: {
|
|
64
|
-
source: '/mnt/user-data/uploads/test.png',
|
|
65
|
-
type: 'image'
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
id: 'test-id'
|
|
69
|
-
}
|
|
70
|
-
} as Request;
|
|
71
|
-
|
|
72
|
-
const res = {
|
|
73
|
-
status: mock(() => res),
|
|
74
|
-
json: mock(() => {}),
|
|
75
|
-
} as unknown as Response;
|
|
76
|
-
|
|
77
|
-
const next = mock(() => {}) as NextFunction;
|
|
78
|
-
|
|
79
|
-
await fileInterceptorMiddleware(req, res, next);
|
|
80
|
-
|
|
81
|
-
// Should provide helpful error when file not found
|
|
82
|
-
expect(res.status).toHaveBeenCalledWith(400);
|
|
83
|
-
expect(res.json).toHaveBeenCalledWith({
|
|
84
|
-
jsonrpc: '2.0',
|
|
85
|
-
error: {
|
|
86
|
-
code: -32602,
|
|
87
|
-
message: 'File not accessible via HTTP transport',
|
|
88
|
-
data: {
|
|
89
|
-
path: '/mnt/user-data/uploads/test.png',
|
|
90
|
-
suggestions: expect.arrayContaining([
|
|
91
|
-
'Upload the file using the /mcp/upload endpoint first'
|
|
92
|
-
])
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
id: 'test-id'
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('should auto-upload local files in HTTP mode', async () => {
|
|
100
|
-
// For now, let's test that the middleware doesn't break when Cloudflare is not configured
|
|
101
|
-
// This is actually the expected behavior in a test environment
|
|
102
|
-
const req = {
|
|
103
|
-
body: {
|
|
104
|
-
method: 'tools/call',
|
|
105
|
-
params: {
|
|
106
|
-
arguments: {
|
|
107
|
-
source: '/local/path/image.jpg'
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
} as Request;
|
|
112
|
-
|
|
113
|
-
const res = {} as Response;
|
|
114
|
-
const next = mock(() => {}) as NextFunction;
|
|
115
|
-
|
|
116
|
-
await fileInterceptorMiddleware(req, res, next);
|
|
117
|
-
|
|
118
|
-
// Without proper Cloudflare configuration, the path should remain unchanged
|
|
119
|
-
// This is the correct behavior for the current implementation
|
|
120
|
-
expect(req.body.params.arguments.source).toBe('/local/path/image.jpg');
|
|
121
|
-
expect(next).toHaveBeenCalled();
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should skip non-file fields', async () => {
|
|
125
|
-
const originalSource = 'https://example.com/image.jpg';
|
|
126
|
-
const req = {
|
|
127
|
-
body: {
|
|
128
|
-
method: 'tools/call',
|
|
129
|
-
params: {
|
|
130
|
-
arguments: {
|
|
131
|
-
source: originalSource,
|
|
132
|
-
otherField: 'some value'
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
} as Request;
|
|
137
|
-
|
|
138
|
-
const res = {} as Response;
|
|
139
|
-
const next = mock(() => {}) as NextFunction;
|
|
140
|
-
|
|
141
|
-
await fileInterceptorMiddleware(req, res, next);
|
|
142
|
-
|
|
143
|
-
// Should not modify URL sources
|
|
144
|
-
expect(req.body.params.arguments.source).toBe(originalSource);
|
|
145
|
-
expect(next).toHaveBeenCalled();
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('should skip non-tool-call requests', async () => {
|
|
149
|
-
const req = {
|
|
150
|
-
body: {
|
|
151
|
-
method: 'initialize',
|
|
152
|
-
params: {}
|
|
153
|
-
}
|
|
154
|
-
} as Request;
|
|
155
|
-
|
|
156
|
-
const res = {} as Response;
|
|
157
|
-
const next = mock(() => {}) as NextFunction;
|
|
158
|
-
|
|
159
|
-
await fileInterceptorMiddleware(req, res, next);
|
|
160
|
-
|
|
161
|
-
expect(next).toHaveBeenCalled();
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('should handle multiple file fields', async () => {
|
|
165
|
-
// Test that middleware processes multiple fields without breaking
|
|
166
|
-
const req = {
|
|
167
|
-
body: {
|
|
168
|
-
method: 'tools/call',
|
|
169
|
-
params: {
|
|
170
|
-
arguments: {
|
|
171
|
-
source1: '/path/image1.jpg',
|
|
172
|
-
source2: '/path/image2.png',
|
|
173
|
-
normalField: 'value'
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
} as Request;
|
|
178
|
-
|
|
179
|
-
const res = {} as Response;
|
|
180
|
-
const next = mock(() => {}) as NextFunction;
|
|
181
|
-
|
|
182
|
-
await fileInterceptorMiddleware(req, res, next);
|
|
183
|
-
|
|
184
|
-
// Without proper Cloudflare configuration, paths should remain unchanged
|
|
185
|
-
expect(req.body.params.arguments.source1).toBe('/path/image1.jpg');
|
|
186
|
-
expect(req.body.params.arguments.source2).toBe('/path/image2.png');
|
|
187
|
-
expect(req.body.params.arguments.normalField).toBe('value');
|
|
188
|
-
expect(next).toHaveBeenCalled();
|
|
189
|
-
});
|
|
190
|
-
});
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll, mock } from "bun:test";
|
|
2
|
-
|
|
3
|
-
// Logger is mocked globally in setup.ts
|
|
4
|
-
|
|
5
|
-
import { createServer } from "../../src/server.js";
|
|
6
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
-
|
|
8
|
-
describe("MCP Server Integration", () => {
|
|
9
|
-
let server: McpServer;
|
|
10
|
-
|
|
11
|
-
beforeAll(async () => {
|
|
12
|
-
process.env.GOOGLE_GEMINI_API_KEY = "test-key";
|
|
13
|
-
server = await createServer();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
afterAll(() => {
|
|
17
|
-
delete process.env.GOOGLE_GEMINI_API_KEY;
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("should create server successfully", () => {
|
|
21
|
-
expect(server).toBeDefined();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("should be properly configured", () => {
|
|
25
|
-
expect(server).toBeInstanceOf(McpServer);
|
|
26
|
-
});
|
|
27
|
-
});
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
|
|
2
|
-
import { testServerManager } from "../utils/test-server-manager.js";
|
|
3
|
-
|
|
4
|
-
describe("SSE Transport Integration", () => {
|
|
5
|
-
let baseUrl: string;
|
|
6
|
-
|
|
7
|
-
beforeAll(async () => {
|
|
8
|
-
const testServer = await testServerManager.startTestServer({
|
|
9
|
-
sessionMode: "stateful",
|
|
10
|
-
enableSse: true,
|
|
11
|
-
enableJsonResponse: true,
|
|
12
|
-
enableSseFallback: true,
|
|
13
|
-
ssePaths: {
|
|
14
|
-
stream: "/sse",
|
|
15
|
-
message: "/messages"
|
|
16
|
-
},
|
|
17
|
-
security: {
|
|
18
|
-
enableCors: true,
|
|
19
|
-
enableDnsRebindingProtection: true,
|
|
20
|
-
allowedHosts: ["127.0.0.1", "localhost"]
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
baseUrl = testServer.baseUrl;
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
afterAll(async () => {
|
|
28
|
-
await testServerManager.stopAllServers();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe("health endpoint", () => {
|
|
32
|
-
it("should include SSE fallback status in health check", async () => {
|
|
33
|
-
const response = await fetch(`${baseUrl}/health`);
|
|
34
|
-
const health = await response.json() as {
|
|
35
|
-
status: string;
|
|
36
|
-
transport: string;
|
|
37
|
-
sseFallback: string;
|
|
38
|
-
ssePaths: { stream: string; message: string; };
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
expect(health.status).toBe("healthy");
|
|
42
|
-
expect(health.transport).toBe("streamable-http");
|
|
43
|
-
expect(health.sseFallback).toBe("enabled");
|
|
44
|
-
expect(health.ssePaths).toEqual({
|
|
45
|
-
stream: "/sse",
|
|
46
|
-
message: "/messages"
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe("SSE endpoint availability", () => {
|
|
52
|
-
it("should reject GET /sse in stateless mode", async () => {
|
|
53
|
-
const response = await fetch(`${baseUrl}/sse`, {
|
|
54
|
-
method: "GET"
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// In current stateful mode, we expect different behavior
|
|
58
|
-
// This test would need a separate server instance with stateless config
|
|
59
|
-
// For now, just verify the endpoint exists
|
|
60
|
-
expect(response.status).toBeDefined();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("should reject POST /messages without sessionId", async () => {
|
|
64
|
-
const response = await fetch(`${baseUrl}/messages`, {
|
|
65
|
-
method: "POST",
|
|
66
|
-
headers: {
|
|
67
|
-
"Content-Type": "application/json"
|
|
68
|
-
},
|
|
69
|
-
body: JSON.stringify({
|
|
70
|
-
jsonrpc: "2.0",
|
|
71
|
-
method: "initialize",
|
|
72
|
-
id: 1
|
|
73
|
-
})
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
expect(response.status).toBe(400);
|
|
77
|
-
const error = await response.json() as { error: { message: string } };
|
|
78
|
-
expect(error.error.message).toContain("Missing sessionId");
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("should reject POST /messages with invalid sessionId", async () => {
|
|
82
|
-
const response = await fetch(`${baseUrl}/messages?sessionId=invalid`, {
|
|
83
|
-
method: "POST",
|
|
84
|
-
headers: {
|
|
85
|
-
"Content-Type": "application/json"
|
|
86
|
-
},
|
|
87
|
-
body: JSON.stringify({
|
|
88
|
-
jsonrpc: "2.0",
|
|
89
|
-
method: "initialize",
|
|
90
|
-
id: 1
|
|
91
|
-
})
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
expect(response.status).toBe(400);
|
|
95
|
-
const error = await response.json() as { error: { message: string } };
|
|
96
|
-
expect(error.error.message).toContain("No active SSE session");
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
describe("transport mixing prevention", () => {
|
|
101
|
-
it("should prevent using streamable HTTP session ID on SSE endpoints", async () => {
|
|
102
|
-
// First create a streamable HTTP session
|
|
103
|
-
const initResponse = await fetch(`${baseUrl}/mcp`, {
|
|
104
|
-
method: "POST",
|
|
105
|
-
headers: {
|
|
106
|
-
"Content-Type": "application/json"
|
|
107
|
-
},
|
|
108
|
-
body: JSON.stringify({
|
|
109
|
-
jsonrpc: "2.0",
|
|
110
|
-
method: "initialize",
|
|
111
|
-
params: {
|
|
112
|
-
protocolVersion: "2024-11-05",
|
|
113
|
-
capabilities: {},
|
|
114
|
-
clientInfo: { name: "test", version: "1.0.0" }
|
|
115
|
-
},
|
|
116
|
-
id: 1
|
|
117
|
-
})
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
const sessionId = initResponse.headers.get("Mcp-Session-Id");
|
|
121
|
-
|
|
122
|
-
if (sessionId) {
|
|
123
|
-
// Try to use this session ID on SSE message endpoint
|
|
124
|
-
const response = await fetch(`${baseUrl}/messages?sessionId=${sessionId}`, {
|
|
125
|
-
method: "POST",
|
|
126
|
-
headers: {
|
|
127
|
-
"Content-Type": "application/json"
|
|
128
|
-
},
|
|
129
|
-
body: JSON.stringify({
|
|
130
|
-
jsonrpc: "2.0",
|
|
131
|
-
method: "ping",
|
|
132
|
-
id: 2
|
|
133
|
-
})
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
expect(response.status).toBe(400);
|
|
137
|
-
const error = await response.json() as { error: { message: string } };
|
|
138
|
-
expect(error.error.message).toContain("streamable HTTP transport");
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
});
|
package/tests/setup.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { beforeAll, afterAll, mock } from "bun:test";
|
|
2
|
-
import { MockHelpers } from "./utils/mock-helpers.js";
|
|
3
|
-
|
|
4
|
-
// Global mock instances
|
|
5
|
-
export const globalMocks = {
|
|
6
|
-
logger: MockHelpers.createLoggerMock(),
|
|
7
|
-
fs: MockHelpers.createFileSystemMock(),
|
|
8
|
-
geminiClient: MockHelpers.createGeminiClientMock()
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
// Mock logger globally for all tests
|
|
12
|
-
mock.module("@/utils/logger", () => ({
|
|
13
|
-
logger: globalMocks.logger
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
// Mock fs module for Bun compatibility
|
|
17
|
-
mock.module("fs", () => globalMocks.fs);
|
|
18
|
-
|
|
19
|
-
// Mock Google Gemini client
|
|
20
|
-
mock.module("@google/generative-ai", () => ({
|
|
21
|
-
GoogleGenerativeAI: mock(() => ({
|
|
22
|
-
getGenerativeModel: globalMocks.geminiClient.getGenerativeModel
|
|
23
|
-
}))
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
|
-
beforeAll(() => {
|
|
27
|
-
// Set up test environment variables
|
|
28
|
-
process.env.GOOGLE_GEMINI_API_KEY = "test-api-key";
|
|
29
|
-
process.env.LOG_LEVEL = "error";
|
|
30
|
-
process.env.NODE_ENV = "test";
|
|
31
|
-
process.env.CLOUDFLARE_R2_ACCOUNT_ID = "test-account";
|
|
32
|
-
process.env.CLOUDFLARE_R2_ACCESS_KEY_ID = "test-access-key";
|
|
33
|
-
process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY = "test-secret";
|
|
34
|
-
process.env.CLOUDFLARE_R2_BUCKET = "test-bucket";
|
|
35
|
-
|
|
36
|
-
// Initialize global test state
|
|
37
|
-
(globalThis as any).__TEST_MODE__ = true;
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
afterAll(() => {
|
|
41
|
-
// Clean up environment variables
|
|
42
|
-
delete process.env.GOOGLE_GEMINI_API_KEY;
|
|
43
|
-
delete process.env.LOG_LEVEL;
|
|
44
|
-
delete process.env.NODE_ENV;
|
|
45
|
-
delete process.env.CLOUDFLARE_R2_ACCOUNT_ID;
|
|
46
|
-
delete process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
|
|
47
|
-
delete process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
|
|
48
|
-
delete process.env.CLOUDFLARE_R2_BUCKET;
|
|
49
|
-
|
|
50
|
-
// Clean up global test state
|
|
51
|
-
delete (globalThis as any).__TEST_MODE__;
|
|
52
|
-
|
|
53
|
-
// Reset all mocks
|
|
54
|
-
MockHelpers.resetAllMocks(globalMocks);
|
|
55
|
-
});
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type definitions for API responses used in tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface HealthCheckResponse {
|
|
6
|
-
status: string;
|
|
7
|
-
transport: string;
|
|
8
|
-
sseFallback: string;
|
|
9
|
-
ssePaths: {
|
|
10
|
-
stream: string;
|
|
11
|
-
message: string;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface ErrorResponse {
|
|
16
|
-
error: {
|
|
17
|
-
message: string;
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface MCPResponse {
|
|
22
|
-
jsonrpc: string;
|
|
23
|
-
id: number;
|
|
24
|
-
result?: any;
|
|
25
|
-
error?: {
|
|
26
|
-
code: number;
|
|
27
|
-
message: string;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface SessionResponse {
|
|
32
|
-
sessionId: string;
|
|
33
|
-
transport: string;
|
|
34
|
-
mode: string;
|
|
35
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
// Common test types to improve type safety across test files
|
|
2
|
-
|
|
3
|
-
export interface MockError {
|
|
4
|
-
message: string;
|
|
5
|
-
code?: string | number;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface MockGeminiResponse {
|
|
9
|
-
summary: string;
|
|
10
|
-
details: string;
|
|
11
|
-
technical_details?: Record<string, any>;
|
|
12
|
-
confidence: number;
|
|
13
|
-
recommendations?: string[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface MockComparisonResponse {
|
|
17
|
-
summary: string;
|
|
18
|
-
differences: any[];
|
|
19
|
-
similarity_score: number;
|
|
20
|
-
analysis_method: string;
|
|
21
|
-
recommendations: string[];
|
|
22
|
-
technical_details: Record<string, string>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface MockAnalysisRequest {
|
|
26
|
-
input: string;
|
|
27
|
-
detail_level: 'quick' | 'detailed';
|
|
28
|
-
custom_prompt?: string;
|
|
29
|
-
max_frames?: number;
|
|
30
|
-
source?: string;
|
|
31
|
-
type?: 'image' | 'video' | 'gif';
|
|
32
|
-
prompt?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface MockCompareRequest {
|
|
36
|
-
input1: string;
|
|
37
|
-
input2: string;
|
|
38
|
-
comparison_type: 'pixel' | 'structural' | 'semantic';
|
|
39
|
-
custom_prompt?: string;
|
|
40
|
-
source1?: string;
|
|
41
|
-
source2?: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface MockHttpResponseData {
|
|
45
|
-
status?: string;
|
|
46
|
-
data?: any;
|
|
47
|
-
error?: string;
|
|
48
|
-
[key: string]: any;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface MockS3Command {
|
|
52
|
-
Bucket: string;
|
|
53
|
-
Key: string;
|
|
54
|
-
Body?: Buffer | string;
|
|
55
|
-
ContentType?: string;
|
|
56
|
-
[key: string]: any;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface MockCloudflareR2Client {
|
|
60
|
-
s3Client: {
|
|
61
|
-
send: (command: MockS3Command) => Promise<any>;
|
|
62
|
-
};
|
|
63
|
-
uploadFile: (buffer: Buffer, filename: string) => Promise<string>;
|
|
64
|
-
uploadBase64: (data: string, mimeType: string, filename?: string) => Promise<string>;
|
|
65
|
-
isConfigured: () => boolean;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface MockSSEConfig {
|
|
69
|
-
security?: {
|
|
70
|
-
enableCors?: boolean;
|
|
71
|
-
enableDnsRebindingProtection?: boolean;
|
|
72
|
-
allowedHosts?: string[];
|
|
73
|
-
};
|
|
74
|
-
sessionMode?: 'stateful' | 'stateless';
|
|
75
|
-
enableSse?: boolean;
|
|
76
|
-
enableJsonResponse?: boolean;
|
|
77
|
-
enableSseFallback?: boolean;
|
|
78
|
-
ssePaths?: {
|
|
79
|
-
stream: string;
|
|
80
|
-
message: string;
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Generic mock function type
|
|
85
|
-
export type MockFunction<T extends (...args: any[]) => any> = T & {
|
|
86
|
-
mock: {
|
|
87
|
-
calls: Parameters<T>[];
|
|
88
|
-
results: { value: ReturnType<T> }[];
|
|
89
|
-
};
|
|
90
|
-
mockRestore?: () => void;
|
|
91
|
-
mockImplementation?: (impl: T) => void;
|
|
92
|
-
mockRejectedValue?: (value: any) => void;
|
|
93
|
-
mockRejectedValueOnce?: (value: any) => void;
|
|
94
|
-
mockResolvedValue?: (value: ReturnType<T>) => void;
|
|
95
|
-
mockResolvedValueOnce?: (value: ReturnType<T>) => void;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// Extend global types for test environment
|
|
99
|
-
declare global {
|
|
100
|
-
namespace globalThis {
|
|
101
|
-
var __TEST_MODE__: boolean;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export {};
|