@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.
Files changed (128) hide show
  1. package/README.md +261 -19
  2. package/bin/human-mcp.js +2 -0
  3. package/dist/index.js +65180 -1698
  4. package/package.json +19 -2
  5. package/.claude/agents/code-reviewer.md +0 -140
  6. package/.claude/agents/database-admin.md +0 -86
  7. package/.claude/agents/debugger.md +0 -119
  8. package/.claude/agents/docs-manager.md +0 -113
  9. package/.claude/agents/git-manager.md +0 -59
  10. package/.claude/agents/planner-researcher.md +0 -97
  11. package/.claude/agents/project-manager.md +0 -113
  12. package/.claude/agents/tester.md +0 -95
  13. package/.claude/commands/cook.md +0 -7
  14. package/.claude/commands/debug.md +0 -10
  15. package/.claude/commands/docs/init.md +0 -11
  16. package/.claude/commands/docs/update.md +0 -11
  17. package/.claude/commands/fix/ci.md +0 -8
  18. package/.claude/commands/fix/fast.md +0 -5
  19. package/.claude/commands/fix/hard.md +0 -7
  20. package/.claude/commands/fix/test.md +0 -16
  21. package/.claude/commands/git/cm.md +0 -5
  22. package/.claude/commands/git/cp.md +0 -4
  23. package/.claude/commands/plan/ci.md +0 -12
  24. package/.claude/commands/plan/two.md +0 -13
  25. package/.claude/commands/plan.md +0 -10
  26. package/.claude/commands/test.md +0 -7
  27. package/.claude/commands/watzup.md +0 -8
  28. package/.claude/hooks/telegram_notify.sh +0 -136
  29. package/.claude/send-discord.sh +0 -64
  30. package/.claude/settings.json +0 -7
  31. package/.claude/statusline.sh +0 -143
  32. package/.dockerignore +0 -81
  33. package/.env.example +0 -44
  34. package/.github/workflows/publish.yml +0 -88
  35. package/.opencode/agent/code-reviewer.md +0 -142
  36. package/.opencode/agent/debugger.md +0 -74
  37. package/.opencode/agent/docs-manager.md +0 -119
  38. package/.opencode/agent/git-manager.md +0 -60
  39. package/.opencode/agent/planner-researcher.md +0 -100
  40. package/.opencode/agent/project-manager.md +0 -113
  41. package/.opencode/agent/system-architecture.md +0 -200
  42. package/.opencode/agent/tester.md +0 -96
  43. package/.opencode/agent/ui-ux-developer.md +0 -97
  44. package/.opencode/command/cook.md +0 -7
  45. package/.opencode/command/debug.md +0 -10
  46. package/.opencode/command/fix/ci.md +0 -8
  47. package/.opencode/command/fix/fast.md +0 -5
  48. package/.opencode/command/fix/hard.md +0 -7
  49. package/.opencode/command/fix/test.md +0 -16
  50. package/.opencode/command/git/cm.md +0 -5
  51. package/.opencode/command/git/cp.md +0 -4
  52. package/.opencode/command/plan/ci.md +0 -12
  53. package/.opencode/command/plan/two.md +0 -13
  54. package/.opencode/command/plan.md +0 -10
  55. package/.opencode/command/test.md +0 -7
  56. package/.opencode/command/watzup.md +0 -8
  57. package/.releaserc.json +0 -26
  58. package/.serena/project.yml +0 -68
  59. package/CHANGELOG.md +0 -62
  60. package/CLAUDE.md +0 -141
  61. package/DEPLOYMENT.md +0 -329
  62. package/Dockerfile +0 -52
  63. package/QUICKSTART.md +0 -97
  64. package/bun.lock +0 -1872
  65. package/bunfig.toml +0 -15
  66. package/docker-compose.yaml +0 -128
  67. package/docs/README.md +0 -51
  68. package/docs/codebase-structure-architecture-code-standards.md +0 -428
  69. package/docs/codebase-summary.md +0 -321
  70. package/docs/project-overview-pdr.md +0 -286
  71. package/docs/project-roadmap.md +0 -494
  72. package/examples/debugging-session.ts +0 -96
  73. package/human-mcp.png +0 -0
  74. package/inspector-wrapper.mjs +0 -33
  75. package/plans/001-streamable-http-transport-plan.md +0 -905
  76. package/plans/002-sse-fallback-http-transport-plan.md +0 -161
  77. package/plans/003-fix-test-infrastructure-and-ci-plan.md +0 -699
  78. package/plans/003-http-transport-local-file-access-plan.md +0 -880
  79. package/plans/004-fix-typescript-compilation-errors-plan.md +0 -388
  80. package/plans/005-comprehensive-test-infrastructure-fix-plan.md +0 -854
  81. package/plans/templates/bug-fix-template.md +0 -69
  82. package/plans/templates/feature-implementation-template.md +0 -84
  83. package/plans/templates/refactor-template.md +0 -82
  84. package/plans/templates/template-usage-guide.md +0 -58
  85. package/src/index.ts +0 -49
  86. package/src/prompts/debugging-prompts.ts +0 -149
  87. package/src/prompts/index.ts +0 -55
  88. package/src/resources/documentation.ts +0 -316
  89. package/src/resources/index.ts +0 -49
  90. package/src/server.ts +0 -36
  91. package/src/tools/eyes/index.ts +0 -225
  92. package/src/tools/eyes/processors/gif.ts +0 -137
  93. package/src/tools/eyes/processors/image.ts +0 -213
  94. package/src/tools/eyes/processors/video.ts +0 -135
  95. package/src/tools/eyes/schemas.ts +0 -51
  96. package/src/tools/eyes/utils/formatters.ts +0 -126
  97. package/src/tools/eyes/utils/gemini-client.ts +0 -73
  98. package/src/transports/http/file-interceptor.ts +0 -134
  99. package/src/transports/http/middleware.ts +0 -46
  100. package/src/transports/http/routes.ts +0 -297
  101. package/src/transports/http/server.ts +0 -116
  102. package/src/transports/http/session.ts +0 -93
  103. package/src/transports/http/sse-routes.ts +0 -210
  104. package/src/transports/index.ts +0 -36
  105. package/src/transports/stdio.ts +0 -7
  106. package/src/transports/types.ts +0 -50
  107. package/src/types/index.ts +0 -41
  108. package/src/utils/cloudflare-r2.ts +0 -107
  109. package/src/utils/config.ts +0 -123
  110. package/src/utils/errors.ts +0 -40
  111. package/src/utils/logger.ts +0 -49
  112. package/tests/integration/http-transport-files.test.ts +0 -190
  113. package/tests/integration/server.test.ts +0 -27
  114. package/tests/integration/sse-transport.test.ts +0 -142
  115. package/tests/setup.ts +0 -55
  116. package/tests/types/api-responses.ts +0 -35
  117. package/tests/types/test-types.ts +0 -105
  118. package/tests/unit/cloudflare-r2.test.ts +0 -118
  119. package/tests/unit/config.test.ts +0 -40
  120. package/tests/unit/eyes-analyze.test.ts +0 -150
  121. package/tests/unit/formatters.test.ts +0 -85
  122. package/tests/unit/sse-routes.test.ts +0 -92
  123. package/tests/utils/error-scenarios.ts +0 -198
  124. package/tests/utils/index.ts +0 -3
  125. package/tests/utils/mock-helpers.ts +0 -99
  126. package/tests/utils/test-data-generators.ts +0 -217
  127. package/tests/utils/test-server-manager.ts +0 -172
  128. 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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }