@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,93 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
|
-
import type { SessionStore, TransportSession, HttpTransportConfig } from "../types.js";
|
|
5
|
-
|
|
6
|
-
export class SessionManager {
|
|
7
|
-
private transports: Map<string, StreamableHTTPServerTransport>;
|
|
8
|
-
private sessionMode: 'stateful' | 'stateless';
|
|
9
|
-
private store?: SessionStore;
|
|
10
|
-
private config: HttpTransportConfig;
|
|
11
|
-
|
|
12
|
-
constructor(sessionMode: 'stateful' | 'stateless', config: HttpTransportConfig, store?: SessionStore) {
|
|
13
|
-
this.transports = new Map();
|
|
14
|
-
this.sessionMode = sessionMode;
|
|
15
|
-
this.config = config;
|
|
16
|
-
this.store = store;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async createSession(mcpServer: McpServer): Promise<{ transport: StreamableHTTPServerTransport, sessionId: string }> {
|
|
20
|
-
const sessionId = randomUUID();
|
|
21
|
-
|
|
22
|
-
const transport = new StreamableHTTPServerTransport({
|
|
23
|
-
sessionIdGenerator: () => sessionId,
|
|
24
|
-
enableJsonResponse: this.config.enableJsonResponse,
|
|
25
|
-
enableDnsRebindingProtection: this.config.security?.enableDnsRebindingProtection ?? true,
|
|
26
|
-
allowedHosts: this.config.security?.allowedHosts ?? ['127.0.0.1', 'localhost'],
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// Store the transport by the generated session ID
|
|
30
|
-
this.transports.set(sessionId, transport);
|
|
31
|
-
|
|
32
|
-
transport.onclose = () => {
|
|
33
|
-
this.terminateSession(sessionId);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
if (this.store) {
|
|
37
|
-
await this.store.set(sessionId, {
|
|
38
|
-
id: sessionId,
|
|
39
|
-
createdAt: Date.now(),
|
|
40
|
-
transport: transport
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
await mcpServer.connect(transport);
|
|
45
|
-
|
|
46
|
-
return { transport, sessionId };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async getTransport(sessionId: string): Promise<StreamableHTTPServerTransport | null> {
|
|
50
|
-
let transport = this.transports.get(sessionId);
|
|
51
|
-
|
|
52
|
-
if (!transport && this.store) {
|
|
53
|
-
const session = await this.store.get(sessionId);
|
|
54
|
-
if (session && session.transport) {
|
|
55
|
-
transport = session.transport;
|
|
56
|
-
this.transports.set(sessionId, transport);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return transport || null;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async terminateSession(sessionId: string): Promise<void> {
|
|
64
|
-
const transport = this.transports.get(sessionId);
|
|
65
|
-
if (transport) {
|
|
66
|
-
// Remove from map first to prevent circular cleanup
|
|
67
|
-
this.transports.delete(sessionId);
|
|
68
|
-
// Clear the onclose handler to prevent recursion
|
|
69
|
-
transport.onclose = undefined;
|
|
70
|
-
transport.close();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (this.store) {
|
|
74
|
-
await this.store.delete(sessionId);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async cleanup(): Promise<void> {
|
|
79
|
-
// Create a copy of the map entries to avoid modification during iteration
|
|
80
|
-
const transportEntries = Array.from(this.transports.entries());
|
|
81
|
-
this.transports.clear();
|
|
82
|
-
|
|
83
|
-
for (const [sessionId, transport] of transportEntries) {
|
|
84
|
-
// Clear the onclose handler to prevent recursion during cleanup
|
|
85
|
-
transport.onclose = undefined;
|
|
86
|
-
transport.close();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (this.store) {
|
|
90
|
-
await this.store.cleanup();
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { Router } from "express";
|
|
2
|
-
import type { Request, Response } from "express";
|
|
3
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
-
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
5
|
-
import type { HttpTransportConfig } from "../types.js";
|
|
6
|
-
import type { SessionManager } from "./session.js";
|
|
7
|
-
|
|
8
|
-
interface SSESession {
|
|
9
|
-
transport: SSEServerTransport;
|
|
10
|
-
createdAt: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class SSEManager {
|
|
14
|
-
private sessions = new Map<string, SSESession>();
|
|
15
|
-
|
|
16
|
-
constructor(private config: HttpTransportConfig) {}
|
|
17
|
-
|
|
18
|
-
hasSession(sessionId: string): boolean {
|
|
19
|
-
return this.sessions.has(sessionId);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
createSession(endpoint: string, res: Response): SSEServerTransport {
|
|
23
|
-
const transport = new SSEServerTransport(endpoint, res, {
|
|
24
|
-
allowedHosts: this.config.security?.allowedHosts,
|
|
25
|
-
allowedOrigins: this.config.security?.corsOrigins,
|
|
26
|
-
enableDnsRebindingProtection: this.config.security?.enableDnsRebindingProtection
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const session: SSESession = {
|
|
30
|
-
transport,
|
|
31
|
-
createdAt: Date.now()
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
this.sessions.set(transport.sessionId, session);
|
|
35
|
-
|
|
36
|
-
// Cleanup on close
|
|
37
|
-
transport.onclose = () => {
|
|
38
|
-
this.sessions.delete(transport.sessionId);
|
|
39
|
-
console.log(`SSE session ${transport.sessionId} closed`);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
transport.onerror = (error) => {
|
|
43
|
-
console.error(`SSE session ${transport.sessionId} error:`, error);
|
|
44
|
-
this.sessions.delete(transport.sessionId);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
console.log(`SSE session ${transport.sessionId} created`);
|
|
48
|
-
return transport;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
getSession(sessionId: string): SSEServerTransport | null {
|
|
52
|
-
const session = this.sessions.get(sessionId);
|
|
53
|
-
return session?.transport || null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async cleanup(): Promise<void> {
|
|
57
|
-
const promises = Array.from(this.sessions.values()).map(session =>
|
|
58
|
-
session.transport.close()
|
|
59
|
-
);
|
|
60
|
-
await Promise.all(promises);
|
|
61
|
-
this.sessions.clear();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
getSessionCount(): number {
|
|
65
|
-
return this.sessions.size;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function createSSERoutes(
|
|
70
|
-
mcpServer: McpServer,
|
|
71
|
-
config: HttpTransportConfig,
|
|
72
|
-
streamableSessionManager: SessionManager
|
|
73
|
-
): Router {
|
|
74
|
-
const router = Router();
|
|
75
|
-
const sseManager = new SSEManager(config);
|
|
76
|
-
|
|
77
|
-
if (!config.ssePaths) {
|
|
78
|
-
throw new Error("SSE paths configuration is required");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const { stream: streamPath, message: messagePath } = config.ssePaths;
|
|
82
|
-
|
|
83
|
-
// Guard against stateless mode
|
|
84
|
-
const checkStatefulMode = (req: Request, res: Response, next: any) => {
|
|
85
|
-
if (config.sessionMode === 'stateless') {
|
|
86
|
-
return res.status(405).json({
|
|
87
|
-
jsonrpc: "2.0",
|
|
88
|
-
error: {
|
|
89
|
-
code: -32600,
|
|
90
|
-
message: "SSE endpoints not available in stateless mode"
|
|
91
|
-
},
|
|
92
|
-
id: null
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
next();
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// GET /sse - Establish SSE connection
|
|
99
|
-
router.get(streamPath, checkStatefulMode, async (req: Request, res: Response) => {
|
|
100
|
-
try {
|
|
101
|
-
console.log('SSE connection request received');
|
|
102
|
-
|
|
103
|
-
// Set SSE headers
|
|
104
|
-
res.setHeader('Content-Type', 'text/event-stream');
|
|
105
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
106
|
-
res.setHeader('Connection', 'keep-alive');
|
|
107
|
-
|
|
108
|
-
// Set CORS headers for SSE if CORS is enabled
|
|
109
|
-
if (config.security?.enableCors !== false) {
|
|
110
|
-
res.setHeader('Access-Control-Allow-Origin',
|
|
111
|
-
config.security?.corsOrigins?.join(',') || '*');
|
|
112
|
-
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
113
|
-
res.setHeader('Access-Control-Expose-Headers', 'Mcp-Session-Id');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const baseUrl = `${req.protocol}://${req.get('host')}`;
|
|
117
|
-
const messageEndpoint = `${baseUrl}${messagePath}`;
|
|
118
|
-
|
|
119
|
-
const transport = sseManager.createSession(messageEndpoint, res);
|
|
120
|
-
|
|
121
|
-
// Connect transport to MCP server
|
|
122
|
-
await mcpServer.connect(transport);
|
|
123
|
-
|
|
124
|
-
// Start the SSE stream
|
|
125
|
-
await transport.start();
|
|
126
|
-
|
|
127
|
-
// Set up cleanup on connection close
|
|
128
|
-
res.on('close', () => {
|
|
129
|
-
transport.close();
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
} catch (error) {
|
|
133
|
-
console.error('Error establishing SSE connection:', error);
|
|
134
|
-
if (!res.headersSent) {
|
|
135
|
-
res.status(500).json({
|
|
136
|
-
jsonrpc: "2.0",
|
|
137
|
-
error: {
|
|
138
|
-
code: -32603,
|
|
139
|
-
message: "Internal error establishing SSE connection"
|
|
140
|
-
},
|
|
141
|
-
id: null
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
// POST /messages - Handle incoming messages
|
|
148
|
-
router.post(messagePath, checkStatefulMode, async (req: Request, res: Response) => {
|
|
149
|
-
try {
|
|
150
|
-
const sessionId = req.query.sessionId as string;
|
|
151
|
-
|
|
152
|
-
if (!sessionId) {
|
|
153
|
-
return res.status(400).json({
|
|
154
|
-
jsonrpc: "2.0",
|
|
155
|
-
error: {
|
|
156
|
-
code: -32600,
|
|
157
|
-
message: "Missing sessionId query parameter"
|
|
158
|
-
},
|
|
159
|
-
id: null
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Check if sessionId is being used by streamable HTTP transport
|
|
164
|
-
const streamableTransport = await streamableSessionManager.getTransport(sessionId);
|
|
165
|
-
if (streamableTransport) {
|
|
166
|
-
return res.status(400).json({
|
|
167
|
-
jsonrpc: "2.0",
|
|
168
|
-
error: {
|
|
169
|
-
code: -32600,
|
|
170
|
-
message: "Session ID is already in use by streamable HTTP transport"
|
|
171
|
-
},
|
|
172
|
-
id: null
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const transport = sseManager.getSession(sessionId);
|
|
177
|
-
if (!transport) {
|
|
178
|
-
return res.status(400).json({
|
|
179
|
-
jsonrpc: "2.0",
|
|
180
|
-
error: {
|
|
181
|
-
code: -32600,
|
|
182
|
-
message: `No active SSE session found for sessionId: ${sessionId}`
|
|
183
|
-
},
|
|
184
|
-
id: null
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Forward the message to the transport
|
|
189
|
-
await transport.handlePostMessage(req, res, req.body);
|
|
190
|
-
|
|
191
|
-
} catch (error) {
|
|
192
|
-
console.error('Error handling SSE message:', error);
|
|
193
|
-
if (!res.headersSent) {
|
|
194
|
-
res.status(500).json({
|
|
195
|
-
jsonrpc: "2.0",
|
|
196
|
-
error: {
|
|
197
|
-
code: -32603,
|
|
198
|
-
message: "Internal error processing message"
|
|
199
|
-
},
|
|
200
|
-
id: null
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// Store reference to manager for cleanup
|
|
207
|
-
(router as any).sseManager = sseManager;
|
|
208
|
-
|
|
209
|
-
return router;
|
|
210
|
-
}
|
package/src/transports/index.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { startStdioTransport } from "./stdio.js";
|
|
3
|
-
import { startHttpTransport } from "./http/server.js";
|
|
4
|
-
import type { TransportConfig, HttpServerHandle } from "./types.js";
|
|
5
|
-
|
|
6
|
-
export class TransportManager {
|
|
7
|
-
private server: McpServer;
|
|
8
|
-
private config: TransportConfig;
|
|
9
|
-
private httpHandle?: HttpServerHandle;
|
|
10
|
-
|
|
11
|
-
constructor(server: McpServer, config: TransportConfig) {
|
|
12
|
-
this.server = server;
|
|
13
|
-
this.config = config;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async start(): Promise<void> {
|
|
17
|
-
switch (this.config.type) {
|
|
18
|
-
case 'stdio':
|
|
19
|
-
await startStdioTransport(this.server);
|
|
20
|
-
break;
|
|
21
|
-
case 'http':
|
|
22
|
-
this.httpHandle = await startHttpTransport(this.server, this.config.http!);
|
|
23
|
-
break;
|
|
24
|
-
case 'both':
|
|
25
|
-
await startStdioTransport(this.server);
|
|
26
|
-
this.httpHandle = await startHttpTransport(this.server, this.config.http!);
|
|
27
|
-
break;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async stop(): Promise<void> {
|
|
32
|
-
if (this.httpHandle) {
|
|
33
|
-
await this.httpHandle.close();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
package/src/transports/stdio.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
|
|
4
|
-
export async function startStdioTransport(server: McpServer): Promise<void> {
|
|
5
|
-
const transport = new StdioServerTransport();
|
|
6
|
-
await server.connect(transport);
|
|
7
|
-
}
|
package/src/transports/types.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
2
|
-
|
|
3
|
-
export interface TransportConfig {
|
|
4
|
-
type: 'stdio' | 'http' | 'both';
|
|
5
|
-
http?: HttpTransportConfig;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface HttpTransportConfig {
|
|
9
|
-
port: number;
|
|
10
|
-
host?: string;
|
|
11
|
-
sessionMode: 'stateful' | 'stateless';
|
|
12
|
-
enableSse?: boolean;
|
|
13
|
-
enableJsonResponse?: boolean;
|
|
14
|
-
enableSseFallback?: boolean;
|
|
15
|
-
ssePaths?: {
|
|
16
|
-
stream: string;
|
|
17
|
-
message: string;
|
|
18
|
-
};
|
|
19
|
-
security?: SecurityConfig;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface SecurityConfig {
|
|
23
|
-
enableCors?: boolean;
|
|
24
|
-
corsOrigins?: string[];
|
|
25
|
-
enableDnsRebindingProtection?: boolean;
|
|
26
|
-
allowedHosts?: string[];
|
|
27
|
-
enableRateLimiting?: boolean;
|
|
28
|
-
secret?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface TransportSession {
|
|
32
|
-
id: string;
|
|
33
|
-
createdAt: number;
|
|
34
|
-
transport: StreamableHTTPServerTransport;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface SessionStore {
|
|
38
|
-
get(sessionId: string): Promise<TransportSession | null>;
|
|
39
|
-
set(sessionId: string, session: TransportSession): Promise<void>;
|
|
40
|
-
delete(sessionId: string): Promise<void>;
|
|
41
|
-
cleanup(): Promise<void>;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface HttpServerHandle {
|
|
45
|
-
app: any;
|
|
46
|
-
server: any;
|
|
47
|
-
sessionManager: any;
|
|
48
|
-
sseManager?: any;
|
|
49
|
-
close(): Promise<void>;
|
|
50
|
-
}
|
package/src/types/index.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
export interface AnalysisOptions {
|
|
2
|
-
analysis_type: "general" | "ui_debug" | "error_detection" | "accessibility" | "performance" | "layout";
|
|
3
|
-
detail_level: "quick" | "detailed";
|
|
4
|
-
specific_focus?: string;
|
|
5
|
-
extract_text?: boolean;
|
|
6
|
-
detect_ui_elements?: boolean;
|
|
7
|
-
analyze_colors?: boolean;
|
|
8
|
-
check_accessibility?: boolean;
|
|
9
|
-
fetchTimeout?: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface ProcessingResult {
|
|
13
|
-
description: string;
|
|
14
|
-
analysis: string;
|
|
15
|
-
elements: DetectedElement[];
|
|
16
|
-
insights: string[];
|
|
17
|
-
recommendations: string[];
|
|
18
|
-
metadata: {
|
|
19
|
-
processing_time_ms: number;
|
|
20
|
-
model_used: string;
|
|
21
|
-
frames_analyzed?: number;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface DetectedElement {
|
|
26
|
-
type: string;
|
|
27
|
-
location: {
|
|
28
|
-
x: number;
|
|
29
|
-
y: number;
|
|
30
|
-
width: number;
|
|
31
|
-
height: number;
|
|
32
|
-
};
|
|
33
|
-
properties: Record<string, any>;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface VideoOptions extends AnalysisOptions {
|
|
37
|
-
max_frames?: number;
|
|
38
|
-
sample_rate?: number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
2
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
-
import mime from 'mime-types';
|
|
4
|
-
import { logger } from './logger.js';
|
|
5
|
-
|
|
6
|
-
export class CloudflareR2Client {
|
|
7
|
-
private s3Client: S3Client;
|
|
8
|
-
private bucketName: string;
|
|
9
|
-
private baseUrl: string;
|
|
10
|
-
|
|
11
|
-
constructor() {
|
|
12
|
-
// Check if required environment variables are set
|
|
13
|
-
const requiredVars = [
|
|
14
|
-
'CLOUDFLARE_CDN_ACCESS_KEY',
|
|
15
|
-
'CLOUDFLARE_CDN_SECRET_KEY',
|
|
16
|
-
'CLOUDFLARE_CDN_ENDPOINT_URL',
|
|
17
|
-
'CLOUDFLARE_CDN_BUCKET_NAME',
|
|
18
|
-
'CLOUDFLARE_CDN_BASE_URL'
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
const missing = requiredVars.filter(varName => !process.env[varName]);
|
|
22
|
-
if (missing.length > 0) {
|
|
23
|
-
throw new Error(`Missing required Cloudflare R2 environment variables: ${missing.join(', ')}`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const config = {
|
|
27
|
-
region: 'auto',
|
|
28
|
-
endpoint: process.env.CLOUDFLARE_CDN_ENDPOINT_URL,
|
|
29
|
-
credentials: {
|
|
30
|
-
accessKeyId: process.env.CLOUDFLARE_CDN_ACCESS_KEY!,
|
|
31
|
-
secretAccessKey: process.env.CLOUDFLARE_CDN_SECRET_KEY!,
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
this.s3Client = new S3Client(config);
|
|
36
|
-
this.bucketName = process.env.CLOUDFLARE_CDN_BUCKET_NAME!;
|
|
37
|
-
this.baseUrl = process.env.CLOUDFLARE_CDN_BASE_URL!;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async uploadFile(buffer: Buffer, originalName: string): Promise<string> {
|
|
41
|
-
try {
|
|
42
|
-
const fileExtension = originalName.split('.').pop() || 'bin';
|
|
43
|
-
const mimeType = mime.lookup(originalName) || 'application/octet-stream';
|
|
44
|
-
const key = `human-mcp/${uuidv4()}.${fileExtension}`;
|
|
45
|
-
|
|
46
|
-
const command = new PutObjectCommand({
|
|
47
|
-
Bucket: this.bucketName,
|
|
48
|
-
Key: key,
|
|
49
|
-
Body: buffer,
|
|
50
|
-
ContentType: mimeType,
|
|
51
|
-
Metadata: {
|
|
52
|
-
originalName: originalName,
|
|
53
|
-
uploadedAt: new Date().toISOString(),
|
|
54
|
-
source: 'human-mcp-http-transport'
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
await this.s3Client.send(command);
|
|
59
|
-
|
|
60
|
-
const publicUrl = `${this.baseUrl}/${key}`;
|
|
61
|
-
logger.info(`File uploaded to Cloudflare R2: ${publicUrl}`);
|
|
62
|
-
|
|
63
|
-
return publicUrl;
|
|
64
|
-
} catch (error) {
|
|
65
|
-
logger.error('Failed to upload to Cloudflare R2:', error);
|
|
66
|
-
throw new Error(`Failed to upload file: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async uploadBase64(base64Data: string, mimeType: string, originalName?: string): Promise<string> {
|
|
71
|
-
const buffer = Buffer.from(base64Data, 'base64');
|
|
72
|
-
const extension = mimeType.split('/')[1] || 'bin';
|
|
73
|
-
const fileName = originalName || `upload-${Date.now()}.${extension}`;
|
|
74
|
-
|
|
75
|
-
return this.uploadFile(buffer, fileName);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
isConfigured(): boolean {
|
|
79
|
-
try {
|
|
80
|
-
const requiredVars = [
|
|
81
|
-
'CLOUDFLARE_CDN_ACCESS_KEY',
|
|
82
|
-
'CLOUDFLARE_CDN_SECRET_KEY',
|
|
83
|
-
'CLOUDFLARE_CDN_ENDPOINT_URL',
|
|
84
|
-
'CLOUDFLARE_CDN_BUCKET_NAME',
|
|
85
|
-
'CLOUDFLARE_CDN_BASE_URL'
|
|
86
|
-
];
|
|
87
|
-
return requiredVars.every(varName => process.env[varName]);
|
|
88
|
-
} catch {
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Singleton instance with lazy initialization
|
|
95
|
-
let cloudflareR2Instance: CloudflareR2Client | null = null;
|
|
96
|
-
|
|
97
|
-
export function getCloudflareR2(): CloudflareR2Client | null {
|
|
98
|
-
if (!cloudflareR2Instance) {
|
|
99
|
-
try {
|
|
100
|
-
cloudflareR2Instance = new CloudflareR2Client();
|
|
101
|
-
} catch (error) {
|
|
102
|
-
logger.warn('Cloudflare R2 not configured:', error instanceof Error ? error.message : 'Unknown error');
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return cloudflareR2Instance;
|
|
107
|
-
}
|
package/src/utils/config.ts
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
const ConfigSchema = z.object({
|
|
4
|
-
gemini: z.object({
|
|
5
|
-
apiKey: z.string().min(1, "Google Gemini API key is required"),
|
|
6
|
-
model: z.string().default("gemini-2.5-flash"),
|
|
7
|
-
}),
|
|
8
|
-
transport: z.object({
|
|
9
|
-
type: z.enum(["stdio", "http", "both"]).default("stdio"),
|
|
10
|
-
http: z.object({
|
|
11
|
-
enabled: z.boolean().default(false),
|
|
12
|
-
port: z.number().default(3000),
|
|
13
|
-
host: z.string().default("0.0.0.0"),
|
|
14
|
-
sessionMode: z.enum(["stateful", "stateless"]).default("stateful"),
|
|
15
|
-
enableSse: z.boolean().default(true),
|
|
16
|
-
enableJsonResponse: z.boolean().default(true),
|
|
17
|
-
enableSseFallback: z.boolean().default(false),
|
|
18
|
-
ssePaths: z.object({
|
|
19
|
-
stream: z.string().default("/sse"),
|
|
20
|
-
message: z.string().default("/messages")
|
|
21
|
-
}).default({ stream: "/sse", message: "/messages" }),
|
|
22
|
-
security: z.object({
|
|
23
|
-
enableCors: z.boolean().default(true),
|
|
24
|
-
corsOrigins: z.array(z.string()).optional(),
|
|
25
|
-
enableDnsRebindingProtection: z.boolean().default(true),
|
|
26
|
-
allowedHosts: z.array(z.string()).default(["127.0.0.1", "localhost"]),
|
|
27
|
-
enableRateLimiting: z.boolean().default(false),
|
|
28
|
-
secret: z.string().optional(),
|
|
29
|
-
}).optional(),
|
|
30
|
-
}).optional(),
|
|
31
|
-
}),
|
|
32
|
-
server: z.object({
|
|
33
|
-
port: z.number().default(3000),
|
|
34
|
-
maxRequestSize: z.string().default("50MB"),
|
|
35
|
-
enableCaching: z.boolean().default(true),
|
|
36
|
-
cacheTTL: z.number().default(3600),
|
|
37
|
-
requestTimeout: z.number().default(300000), // 5 minutes
|
|
38
|
-
fetchTimeout: z.number().default(60000), // 60 seconds for HTTP requests
|
|
39
|
-
}),
|
|
40
|
-
security: z.object({
|
|
41
|
-
secret: z.string().optional(),
|
|
42
|
-
rateLimitRequests: z.number().default(100),
|
|
43
|
-
rateLimitWindow: z.number().default(60000),
|
|
44
|
-
}),
|
|
45
|
-
logging: z.object({
|
|
46
|
-
level: z.enum(["debug", "info", "warn", "error"]).default("info"),
|
|
47
|
-
}),
|
|
48
|
-
cloudflare: z.object({
|
|
49
|
-
projectName: z.string().optional().default("human-mcp"),
|
|
50
|
-
bucketName: z.string().optional(),
|
|
51
|
-
accessKey: z.string().optional(),
|
|
52
|
-
secretKey: z.string().optional(),
|
|
53
|
-
endpointUrl: z.string().optional(),
|
|
54
|
-
baseUrl: z.string().optional(),
|
|
55
|
-
}).optional(),
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
export type Config = z.infer<typeof ConfigSchema>;
|
|
59
|
-
|
|
60
|
-
export function loadConfig(): Config {
|
|
61
|
-
const corsOrigins = process.env.HTTP_CORS_ORIGINS ?
|
|
62
|
-
process.env.HTTP_CORS_ORIGINS.split(',').map(origin => origin.trim()) :
|
|
63
|
-
undefined;
|
|
64
|
-
|
|
65
|
-
const allowedHosts = process.env.HTTP_ALLOWED_HOSTS ?
|
|
66
|
-
process.env.HTTP_ALLOWED_HOSTS.split(',').map(host => host.trim()) :
|
|
67
|
-
["127.0.0.1", "localhost"];
|
|
68
|
-
|
|
69
|
-
return ConfigSchema.parse({
|
|
70
|
-
gemini: {
|
|
71
|
-
apiKey: process.env.GOOGLE_GEMINI_API_KEY || "",
|
|
72
|
-
model: process.env.GOOGLE_GEMINI_MODEL || "gemini-2.5-flash",
|
|
73
|
-
},
|
|
74
|
-
transport: {
|
|
75
|
-
type: (process.env.TRANSPORT_TYPE as any) || "stdio",
|
|
76
|
-
http: {
|
|
77
|
-
enabled: process.env.TRANSPORT_TYPE === "http" || process.env.TRANSPORT_TYPE === "both",
|
|
78
|
-
port: parseInt(process.env.HTTP_PORT || "3000"),
|
|
79
|
-
host: process.env.HTTP_HOST || "0.0.0.0",
|
|
80
|
-
sessionMode: (process.env.HTTP_SESSION_MODE as any) || "stateful",
|
|
81
|
-
enableSse: process.env.HTTP_ENABLE_SSE !== "false",
|
|
82
|
-
enableJsonResponse: process.env.HTTP_ENABLE_JSON_RESPONSE !== "false",
|
|
83
|
-
enableSseFallback: process.env.HTTP_ENABLE_SSE_FALLBACK === "true",
|
|
84
|
-
ssePaths: {
|
|
85
|
-
stream: process.env.HTTP_SSE_STREAM_PATH || "/sse",
|
|
86
|
-
message: process.env.HTTP_SSE_MESSAGE_PATH || "/messages"
|
|
87
|
-
},
|
|
88
|
-
security: {
|
|
89
|
-
enableCors: process.env.HTTP_CORS_ENABLED !== "false",
|
|
90
|
-
corsOrigins,
|
|
91
|
-
enableDnsRebindingProtection: process.env.HTTP_DNS_REBINDING_ENABLED !== "false",
|
|
92
|
-
allowedHosts,
|
|
93
|
-
enableRateLimiting: process.env.HTTP_ENABLE_RATE_LIMITING === "true",
|
|
94
|
-
secret: process.env.HTTP_SECRET,
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
server: {
|
|
99
|
-
port: parseInt(process.env.PORT || "3000"),
|
|
100
|
-
maxRequestSize: process.env.MAX_REQUEST_SIZE || "50MB",
|
|
101
|
-
enableCaching: process.env.ENABLE_CACHING !== "false",
|
|
102
|
-
cacheTTL: parseInt(process.env.CACHE_TTL || "3600"),
|
|
103
|
-
requestTimeout: parseInt(process.env.REQUEST_TIMEOUT || "300000"),
|
|
104
|
-
fetchTimeout: parseInt(process.env.FETCH_TIMEOUT || "60000"),
|
|
105
|
-
},
|
|
106
|
-
security: {
|
|
107
|
-
secret: process.env.MCP_SECRET,
|
|
108
|
-
rateLimitRequests: parseInt(process.env.RATE_LIMIT_REQUESTS || "100"),
|
|
109
|
-
rateLimitWindow: parseInt(process.env.RATE_LIMIT_WINDOW || "60000"),
|
|
110
|
-
},
|
|
111
|
-
logging: {
|
|
112
|
-
level: (process.env.LOG_LEVEL as any) || "info",
|
|
113
|
-
},
|
|
114
|
-
cloudflare: {
|
|
115
|
-
projectName: process.env.CLOUDFLARE_CDN_PROJECT_NAME || "human-mcp",
|
|
116
|
-
bucketName: process.env.CLOUDFLARE_CDN_BUCKET_NAME,
|
|
117
|
-
accessKey: process.env.CLOUDFLARE_CDN_ACCESS_KEY,
|
|
118
|
-
secretKey: process.env.CLOUDFLARE_CDN_SECRET_KEY,
|
|
119
|
-
endpointUrl: process.env.CLOUDFLARE_CDN_ENDPOINT_URL,
|
|
120
|
-
baseUrl: process.env.CLOUDFLARE_CDN_BASE_URL,
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
}
|