hawkeye-mcp-server 2.2.0 → 2.3.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 (54) hide show
  1. package/README.md +82 -2
  2. package/build/config/config.d.ts +6 -1
  3. package/build/config/config.js +24 -5
  4. package/build/config/config.js.map +1 -1
  5. package/build/db/index.d.ts +51 -0
  6. package/build/db/index.js +174 -0
  7. package/build/db/index.js.map +1 -0
  8. package/build/db/postgres-state-provider.d.ts +52 -0
  9. package/build/db/postgres-state-provider.js +165 -0
  10. package/build/db/postgres-state-provider.js.map +1 -0
  11. package/build/index.d.ts +4 -1
  12. package/build/index.js +17 -84
  13. package/build/index.js.map +1 -1
  14. package/build/mcp-server-factory.d.ts +36 -0
  15. package/build/mcp-server-factory.js +92 -0
  16. package/build/mcp-server-factory.js.map +1 -0
  17. package/build/server.d.ts +9 -0
  18. package/build/server.js +337 -0
  19. package/build/server.js.map +1 -0
  20. package/build/services/auth.service.d.ts +38 -0
  21. package/build/services/auth.service.js +64 -0
  22. package/build/services/auth.service.js.map +1 -1
  23. package/build/services/project.service.d.ts +11 -0
  24. package/build/services/project.service.js +31 -0
  25. package/build/services/project.service.js.map +1 -1
  26. package/build/services/state.service.d.ts +115 -0
  27. package/build/services/state.service.js +172 -0
  28. package/build/services/state.service.js.map +1 -0
  29. package/build/tools/create-connection.d.ts +12 -0
  30. package/build/tools/create-connection.js +6 -0
  31. package/build/tools/create-connection.js.map +1 -1
  32. package/build/tools/create-manual-investigation.js +10 -6
  33. package/build/tools/create-manual-investigation.js.map +1 -1
  34. package/build/tools/get-connection-info.js +1 -0
  35. package/build/tools/get-connection-info.js.map +1 -1
  36. package/build/tools/get-session-link.js +1 -2
  37. package/build/tools/get-session-link.js.map +1 -1
  38. package/build/tools/get-status.js +2 -4
  39. package/build/tools/get-status.js.map +1 -1
  40. package/build/tools/index.js +11 -1
  41. package/build/tools/index.js.map +1 -1
  42. package/build/tools/inspect-session.js +1 -2
  43. package/build/tools/inspect-session.js.map +1 -1
  44. package/build/tools/list-connection-types.js +9 -0
  45. package/build/tools/list-connection-types.js.map +1 -1
  46. package/build/types/hawkeye.d.ts +7 -0
  47. package/build/types/hawkeye.js.map +1 -1
  48. package/build/utils/connection-builders.d.ts +8 -0
  49. package/build/utils/connection-builders.js +48 -1
  50. package/build/utils/connection-builders.js.map +1 -1
  51. package/build/utils/state.d.ts +24 -17
  52. package/build/utils/state.js +63 -72
  53. package/build/utils/state.js.map +1 -1
  54. package/package.json +12 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres-state-provider.js","sourceRoot":"","sources":["../../src/db/postgres-state-provider.ts"],"names":[],"mappings":"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"}
package/build/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Hawkeye MCP Server v2.0
3
+ * Hawkeye MCP Server v2.0 - Stdio Transport
4
4
  * A Model Context Protocol server for AI-powered incident investigation
5
+ *
6
+ * This is the stdio transport entrypoint for use with npm/npx.
7
+ * For HTTP/SSE transport (Kubernetes deployment), use src/server.ts instead.
5
8
  */
6
9
  export {};
package/build/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Hawkeye MCP Server v2.0
3
+ * Hawkeye MCP Server v2.0 - Stdio Transport
4
4
  * A Model Context Protocol server for AI-powered incident investigation
5
+ *
6
+ * This is the stdio transport entrypoint for use with npm/npx.
7
+ * For HTTP/SSE transport (Kubernetes deployment), use src/server.ts instead.
5
8
  */
6
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7
9
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
- import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
9
10
  // Configuration and utilities
10
11
  import { getConfig } from './config/config.js';
11
12
  import { logger } from './utils/logger.js';
12
- import { formatErrorForMCP } from './utils/errors.js';
13
13
  import { HttpClient } from './utils/http-client.js';
14
14
  // Services
15
15
  import { AuthenticationService } from './services/auth.service.js';
@@ -17,10 +17,10 @@ import { ProjectService } from './services/project.service.js';
17
17
  import { SessionService } from './services/session.service.js';
18
18
  import { ConnectionService } from './services/connection.service.js';
19
19
  import { GuidanceService } from './services/guidance.service.js';
20
- // Tools
21
- import { getToolDefinitions } from './tools/index.js';
20
+ // MCP Server Factory
21
+ import { createMcpServer } from './mcp-server-factory.js';
22
22
  /**
23
- * Initialize services
23
+ * Initialize services - creates singleton instances for stdio mode
24
24
  */
25
25
  async function initializeServices() {
26
26
  const config = getConfig();
@@ -48,87 +48,20 @@ async function initializeServices() {
48
48
  async function main() {
49
49
  try {
50
50
  const config = getConfig();
51
+ // Initialize services once for the stdio session
51
52
  const services = await initializeServices();
52
- // Create MCP server
53
- const server = new Server({
54
- name: config.serverName,
55
- version: config.serverVersion,
56
- }, {
57
- capabilities: {
58
- tools: {},
59
- },
60
- });
61
- // Get all tool definitions
62
- const toolDefinitions = getToolDefinitions();
63
- const toolHandlers = new Map(toolDefinitions.map((def) => [def.tool.name, def.handler]));
64
- /**
65
- * Handler for listing available tools
66
- */
67
- server.setRequestHandler(ListToolsRequestSchema, async () => {
68
- const tools = toolDefinitions.map((def) => def.tool);
69
- return { tools };
70
- });
71
- /**
72
- * Handler for tool execution
73
- */
74
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
75
- const { name: toolName, arguments: toolArgs } = request.params;
76
- try {
77
- // Get the tool handler
78
- const handler = toolHandlers.get(toolName);
79
- if (!handler) {
80
- return {
81
- content: [
82
- {
83
- type: 'text',
84
- text: JSON.stringify({
85
- error: {
86
- type: 'validation',
87
- message: `Unknown tool: ${toolName}`,
88
- },
89
- }, null, 2),
90
- },
91
- ],
92
- };
93
- }
94
- // Execute the tool
95
- const toolServices = {
96
- httpClient: services.httpClient,
97
- authService: services.authService,
98
- projectService: services.projectService,
99
- sessionService: services.sessionService,
100
- connectionService: services.connectionService,
101
- guidanceService: services.guidanceService,
102
- investigationStreamService: services.investigationStreamService,
103
- };
104
- const result = await handler(toolServices, toolArgs);
105
- // Return the result
106
- return {
107
- content: [
108
- {
109
- type: 'text',
110
- text: JSON.stringify(result, null, 2),
111
- },
112
- ],
113
- };
114
- }
115
- catch (error) {
116
- logger.error(`Error executing tool ${toolName}`, error);
117
- return {
118
- content: [
119
- {
120
- type: 'text',
121
- text: JSON.stringify(formatErrorForMCP(error), null, 2),
122
- },
123
- ],
124
- };
125
- }
126
- });
127
- // Connect to transport
53
+ // Create MCP server with services
54
+ // In stdio mode, services are singleton (one user per process)
55
+ const { server, toolCount } = createMcpServer({
56
+ serverName: config.serverName,
57
+ serverVersion: config.serverVersion,
58
+ }, () => services // Return the same services instance for all requests
59
+ );
60
+ // Connect to stdio transport
128
61
  const transport = new StdioServerTransport();
129
62
  await server.connect(transport);
130
63
  logger.info(`${config.serverName} v${config.serverVersion} started successfully`);
131
- logger.info(`${toolDefinitions.length} tools registered`);
64
+ logger.info(`${toolCount} tools registered`);
132
65
  }
133
66
  catch (error) {
134
67
  logger.error('Fatal error during server startup', error);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Factory for creating MCP servers
3
+ * Creates McpServer instances with tool handlers configured
4
+ */
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import { type ToolServices } from './tools/index.js';
7
+ /**
8
+ * Configuration for creating an MCP server
9
+ */
10
+ export interface McpServerConfig {
11
+ serverName: string;
12
+ serverVersion: string;
13
+ }
14
+ /**
15
+ * Result of creating an MCP server
16
+ */
17
+ export interface McpServerResult {
18
+ server: McpServer;
19
+ toolCount: number;
20
+ }
21
+ /**
22
+ * Creates an MCP server with all Hawkeye tools registered.
23
+ *
24
+ * This uses McpServer (the high-level API) but registers tools using
25
+ * the underlying Server's setRequestHandler since:
26
+ * 1. We have JSON schemas, not Zod schemas
27
+ * 2. We need to inject services dynamically at call time
28
+ *
29
+ * The deprecation notice says "Only use Server for advanced use cases" -
30
+ * our dependency injection pattern qualifies as an advanced use case.
31
+ *
32
+ * @param config Server configuration (name, version)
33
+ * @param getServices Function that returns ToolServices for the current request
34
+ * @returns McpServer instance with all tools registered
35
+ */
36
+ export declare function createMcpServer(config: McpServerConfig, getServices: () => ToolServices): McpServerResult;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Factory for creating MCP servers
3
+ * Creates McpServer instances with tool handlers configured
4
+ */
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import { ListToolsRequestSchema, CallToolRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
7
+ import { getToolDefinitions } from './tools/index.js';
8
+ import { logger } from './utils/logger.js';
9
+ import { formatErrorForMCP } from './utils/errors.js';
10
+ /**
11
+ * Creates an MCP server with all Hawkeye tools registered.
12
+ *
13
+ * This uses McpServer (the high-level API) but registers tools using
14
+ * the underlying Server's setRequestHandler since:
15
+ * 1. We have JSON schemas, not Zod schemas
16
+ * 2. We need to inject services dynamically at call time
17
+ *
18
+ * The deprecation notice says "Only use Server for advanced use cases" -
19
+ * our dependency injection pattern qualifies as an advanced use case.
20
+ *
21
+ * @param config Server configuration (name, version)
22
+ * @param getServices Function that returns ToolServices for the current request
23
+ * @returns McpServer instance with all tools registered
24
+ */
25
+ export function createMcpServer(config, getServices) {
26
+ const mcpServer = new McpServer({
27
+ name: config.serverName,
28
+ version: config.serverVersion,
29
+ });
30
+ // Get all tool definitions
31
+ const toolDefinitions = getToolDefinitions();
32
+ // Map tool names to handlers for O(1) lookup
33
+ const toolHandlers = new Map(toolDefinitions.map((def) => [def.tool.name, def.handler]));
34
+ // Access the underlying Server instance for custom handlers
35
+ // McpServer wraps Server and exposes it via .server property
36
+ const server = mcpServer.server;
37
+ // Register tools capability before setting handlers
38
+ // This is required by the MCP SDK validation
39
+ server.registerCapabilities({
40
+ tools: {
41
+ listChanged: true,
42
+ },
43
+ });
44
+ // Register ListTools handler
45
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
46
+ return { tools: toolDefinitions.map((def) => def.tool) };
47
+ });
48
+ // Register CallTool handler
49
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
50
+ const { name: toolName, arguments: toolArgs } = request.params;
51
+ try {
52
+ const handler = toolHandlers.get(toolName);
53
+ if (!handler) {
54
+ return {
55
+ content: [{
56
+ type: 'text',
57
+ text: JSON.stringify({
58
+ error: {
59
+ type: 'validation',
60
+ message: `Unknown tool: ${toolName}`,
61
+ },
62
+ }, null, 2),
63
+ }],
64
+ };
65
+ }
66
+ // Get services for the current request (supports per-user injection in HTTP mode)
67
+ const services = getServices();
68
+ const result = await handler(services, toolArgs);
69
+ return {
70
+ content: [{
71
+ type: 'text',
72
+ text: JSON.stringify(result, null, 2),
73
+ }],
74
+ };
75
+ }
76
+ catch (error) {
77
+ logger.error(`Error executing tool ${toolName}`, error);
78
+ return {
79
+ content: [{
80
+ type: 'text',
81
+ text: JSON.stringify(formatErrorForMCP(error), null, 2),
82
+ }],
83
+ };
84
+ }
85
+ });
86
+ logger.info(`Registered ${toolDefinitions.length} tools with McpServer`);
87
+ return {
88
+ server: mcpServer,
89
+ toolCount: toolDefinitions.length,
90
+ };
91
+ }
92
+ //# sourceMappingURL=mcp-server-factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server-factory.js","sourceRoot":"","sources":["../src/mcp-server-factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Hawkeye MCP Server - HTTP/SSE Transport
4
+ * A Model Context Protocol server for AI-powered incident investigation
5
+ *
6
+ * This is the HTTP/SSE server entrypoint for deployment in Kubernetes.
7
+ * For stdio transport (npm package), use src/index.ts instead.
8
+ */
9
+ export {};
@@ -0,0 +1,337 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Hawkeye MCP Server - HTTP/SSE Transport
4
+ * A Model Context Protocol server for AI-powered incident investigation
5
+ *
6
+ * This is the HTTP/SSE server entrypoint for deployment in Kubernetes.
7
+ * For stdio transport (npm package), use src/index.ts instead.
8
+ */
9
+ import express from 'express';
10
+ import cors from 'cors';
11
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
12
+ import { randomUUID } from 'crypto';
13
+ // Configuration and utilities
14
+ import { logger } from './utils/logger.js';
15
+ import { HttpClient } from './utils/http-client.js';
16
+ // Database
17
+ import { initializePool, runMigrations, checkDatabaseHealth, closePool } from './db/index.js';
18
+ import { getPostgresStateProvider } from './db/postgres-state-provider.js';
19
+ // Services
20
+ import { AuthenticationService } from './services/auth.service.js';
21
+ import { ProjectService } from './services/project.service.js';
22
+ import { SessionService } from './services/session.service.js';
23
+ import { ConnectionService } from './services/connection.service.js';
24
+ import { GuidanceService } from './services/guidance.service.js';
25
+ // MCP Server Factory
26
+ import { createMcpServer } from './mcp-server-factory.js';
27
+ function getServerConfig() {
28
+ const port = parseInt(process.env.HTTP_PORT || '3000', 10);
29
+ const hawkeyeBaseUrl = process.env.HAWKEYE_BASE_URL;
30
+ if (!hawkeyeBaseUrl) {
31
+ throw new Error('HAWKEYE_BASE_URL environment variable is required');
32
+ }
33
+ return {
34
+ port,
35
+ hawkeyeBaseUrl,
36
+ serverName: 'hawkeye-mcp-server',
37
+ serverVersion: '2.0.0',
38
+ };
39
+ }
40
+ /**
41
+ * Active sessions map - keyed by session ID
42
+ */
43
+ const activeSessions = new Map();
44
+ /**
45
+ * Extract authentication info from request headers
46
+ * Priority: Bearer token > Password credentials
47
+ */
48
+ function extractAuthInfo(req) {
49
+ // Priority 1: Bearer token (Auth0/OAuth mode)
50
+ const authHeader = req.headers['authorization'];
51
+ if (authHeader?.toLowerCase().startsWith('bearer ')) {
52
+ const token = authHeader.substring(7).trim();
53
+ if (token) {
54
+ return { type: 'bearer', token };
55
+ }
56
+ }
57
+ // Priority 2: Header-based credentials (legacy password mode)
58
+ const email = req.headers['x-hawkeye-email'];
59
+ const password = req.headers['x-hawkeye-password'];
60
+ if (email && password) {
61
+ return { type: 'password', email, password };
62
+ }
63
+ return null;
64
+ }
65
+ /**
66
+ * Authenticate with Hawkeye API and create services for a user
67
+ * Supports both password and bearer token authentication
68
+ */
69
+ async function createUserServices(authInfo, baseUrl, stateProvider) {
70
+ const httpClient = new HttpClient(baseUrl);
71
+ const authService = new AuthenticationService(httpClient);
72
+ let userEmail;
73
+ if (authInfo.type === 'bearer' && authInfo.token) {
74
+ // Bearer token mode (Auth0/OAuth)
75
+ authService.setBearerToken(authInfo.token);
76
+ const userInfo = await authService.validateAndGetUserInfo();
77
+ userEmail = userInfo.email;
78
+ logger.info(`Authenticated via bearer token: ${userEmail}`);
79
+ }
80
+ else if (authInfo.type === 'password' && authInfo.email && authInfo.password) {
81
+ // Password mode (legacy)
82
+ userEmail = authInfo.email;
83
+ authService.config = {
84
+ ...authService.config,
85
+ email: authInfo.email,
86
+ password: authInfo.password,
87
+ };
88
+ await authService.getAccessToken();
89
+ logger.info(`Authenticated via password: ${userEmail}`);
90
+ }
91
+ else {
92
+ throw new Error('Invalid authentication info');
93
+ }
94
+ const projectService = new ProjectService(httpClient, authService);
95
+ const sessionService = new SessionService(httpClient, authService);
96
+ const connectionService = new ConnectionService(httpClient, authService);
97
+ const guidanceService = new GuidanceService();
98
+ // Lazy load investigation stream service
99
+ const { InvestigationStreamService } = await import('./services/investigation-stream.js');
100
+ const investigationStreamService = new InvestigationStreamService(httpClient);
101
+ // Load user's default project from database
102
+ const defaultProjectUuid = await stateProvider.getDefaultProjectUuid(userEmail);
103
+ if (defaultProjectUuid) {
104
+ projectService.setDefaultProjectFromState(defaultProjectUuid);
105
+ logger.info(`Loaded default project for user ${userEmail}: ${defaultProjectUuid}`);
106
+ }
107
+ // Persist future default-project updates per user (HTTP/server mode)
108
+ projectService.setPersistenceContext(stateProvider, userEmail);
109
+ return {
110
+ services: {
111
+ httpClient,
112
+ authService,
113
+ projectService,
114
+ sessionService,
115
+ connectionService,
116
+ guidanceService,
117
+ investigationStreamService,
118
+ },
119
+ userEmail,
120
+ authType: authInfo.type,
121
+ };
122
+ }
123
+ /**
124
+ * Main server setup
125
+ */
126
+ async function main() {
127
+ const config = getServerConfig();
128
+ const app = express();
129
+ // Middleware
130
+ app.use(cors());
131
+ // Note: Don't use express.json() globally - the MCP transport needs raw body
132
+ // Initialize database
133
+ logger.info('Initializing database connection...');
134
+ initializePool();
135
+ await runMigrations();
136
+ logger.info('Database ready');
137
+ // Get state provider
138
+ const stateProvider = getPostgresStateProvider();
139
+ /**
140
+ * Health check endpoint - liveness probe
141
+ */
142
+ app.get('/health', async (_req, res) => {
143
+ try {
144
+ const dbHealthy = await checkDatabaseHealth();
145
+ if (dbHealthy) {
146
+ res.json({ status: 'healthy', database: 'connected' });
147
+ }
148
+ else {
149
+ res.status(503).json({ status: 'unhealthy', database: 'disconnected' });
150
+ }
151
+ }
152
+ catch (error) {
153
+ res.status(503).json({
154
+ status: 'unhealthy',
155
+ error: error instanceof Error ? error.message : 'Unknown error',
156
+ });
157
+ }
158
+ });
159
+ /**
160
+ * Readiness check endpoint - checks database AND Hawkeye API
161
+ */
162
+ app.get('/ready', async (_req, res) => {
163
+ try {
164
+ // Check database
165
+ const dbHealthy = await checkDatabaseHealth();
166
+ if (!dbHealthy) {
167
+ res.status(503).json({ status: 'not ready', reason: 'database disconnected' });
168
+ return;
169
+ }
170
+ // Check Hawkeye API is reachable (just a basic connectivity check)
171
+ const testClient = new HttpClient(config.hawkeyeBaseUrl);
172
+ try {
173
+ // Try to hit a lightweight endpoint - even an auth error means API is reachable
174
+ await testClient.get('/v1/user/login');
175
+ }
176
+ catch (error) {
177
+ // 405 Method Not Allowed or 401 means API is reachable
178
+ if (error.response?.status === 405 || error.response?.status === 401 || error.response?.status === 400) {
179
+ // API is reachable
180
+ }
181
+ else {
182
+ res.status(503).json({ status: 'not ready', reason: 'hawkeye api unreachable' });
183
+ return;
184
+ }
185
+ }
186
+ res.json({ status: 'ready', database: 'connected', hawkeyeApi: 'reachable' });
187
+ }
188
+ catch (error) {
189
+ res.status(503).json({
190
+ status: 'not ready',
191
+ error: error instanceof Error ? error.message : 'Unknown error',
192
+ });
193
+ }
194
+ });
195
+ /**
196
+ * MCP endpoint - handles both GET (SSE) and POST (messages)
197
+ * The StreamableHTTPServerTransport handles session management internally
198
+ *
199
+ * Supports two authentication methods:
200
+ * 1. Bearer token: Authorization: Bearer <token> (Auth0/OAuth)
201
+ * 2. Password: X-Hawkeye-Email and X-Hawkeye-Password headers (legacy)
202
+ */
203
+ app.all('/mcp', async (req, res) => {
204
+ // Extract authentication info
205
+ const authInfo = extractAuthInfo(req);
206
+ if (!authInfo) {
207
+ res.status(401).json({
208
+ error: 'Authentication required',
209
+ message: 'Please provide either Authorization: Bearer <token> header or X-Hawkeye-Email and X-Hawkeye-Password headers',
210
+ });
211
+ return;
212
+ }
213
+ // Check for existing session
214
+ const sessionId = req.headers['mcp-session-id'];
215
+ // If we have a session ID, use the existing session
216
+ if (sessionId && activeSessions.has(sessionId)) {
217
+ const session = activeSessions.get(sessionId);
218
+ // For bearer token auth, we can't easily verify the same token is being used
219
+ // (tokens might be refreshed), so we just verify the session exists
220
+ // For password auth, verify email matches
221
+ if (authInfo.type === 'password' && session.context.userEmail !== authInfo.email) {
222
+ res.status(403).json({
223
+ error: 'Forbidden',
224
+ message: 'Credentials do not match session owner',
225
+ });
226
+ return;
227
+ }
228
+ // Handle the request with existing transport
229
+ await session.transport.handleRequest(req, res);
230
+ return;
231
+ }
232
+ // No session or new session - create new context and transport
233
+ if (req.method === 'DELETE') {
234
+ // Delete for non-existent session is OK
235
+ res.status(204).send();
236
+ return;
237
+ }
238
+ // New session - authenticate and create context
239
+ const authDescription = authInfo.type === 'bearer' ? 'Bearer token' : authInfo.email;
240
+ logger.info(`New MCP connection via ${authInfo.type} auth: ${authDescription}`);
241
+ try {
242
+ // Create user services (authenticate with Hawkeye)
243
+ const { services, userEmail, authType } = await createUserServices(authInfo, config.hawkeyeBaseUrl, stateProvider);
244
+ // Create context
245
+ const context = {
246
+ userEmail,
247
+ services,
248
+ stateProvider,
249
+ authType,
250
+ };
251
+ // Create MCP server using the factory
252
+ // getServices returns this user's services for all tool calls
253
+ const { server: mcpServer } = createMcpServer({
254
+ serverName: config.serverName,
255
+ serverVersion: config.serverVersion,
256
+ }, () => services);
257
+ // Create transport - session ID generator creates new sessions
258
+ const transport = new StreamableHTTPServerTransport({
259
+ sessionIdGenerator: () => randomUUID(),
260
+ onsessioninitialized: (newSessionId) => {
261
+ logger.info(`Session initialized: ${newSessionId} for user ${userEmail} (${authType} auth)`);
262
+ activeSessions.set(newSessionId, { transport, mcpServer, context });
263
+ },
264
+ });
265
+ // Connect server to transport
266
+ await mcpServer.connect(transport);
267
+ // Handle the request
268
+ await transport.handleRequest(req, res);
269
+ }
270
+ catch (error) {
271
+ logger.error('Failed to initialize MCP session', error);
272
+ // Provide more specific error messages for auth failures
273
+ const isAuthError = error instanceof Error &&
274
+ (error.message.includes('Bearer token') || error.message.includes('authentication'));
275
+ res.status(isAuthError ? 401 : 500).json({
276
+ error: isAuthError ? 'Authentication failed' : 'Session initialization failed',
277
+ message: error instanceof Error ? error.message : 'Unknown error',
278
+ });
279
+ }
280
+ });
281
+ /**
282
+ * Legacy SSE endpoint (for backwards compatibility)
283
+ */
284
+ app.get('/sse', (req, res) => {
285
+ res.redirect(307, '/mcp');
286
+ });
287
+ // Error handling middleware
288
+ app.use((err, _req, res, _next) => {
289
+ logger.error('Unhandled error', err);
290
+ res.status(500).json({
291
+ error: 'Internal server error',
292
+ message: err.message,
293
+ });
294
+ });
295
+ // Start server
296
+ const server = app.listen(config.port, () => {
297
+ logger.info(`${config.serverName} v${config.serverVersion} started`);
298
+ logger.info(`HTTP/SSE server listening on port ${config.port}`);
299
+ logger.info(`MCP endpoint: http://localhost:${config.port}/mcp`);
300
+ logger.info(`Health check: http://localhost:${config.port}/health`);
301
+ });
302
+ // Graceful shutdown
303
+ const shutdown = async () => {
304
+ logger.info('Shutting down...');
305
+ // Close all active sessions
306
+ for (const [sessionId, session] of activeSessions) {
307
+ try {
308
+ await session.transport.close();
309
+ logger.info(`Closed session: ${sessionId}`);
310
+ }
311
+ catch (error) {
312
+ logger.error(`Error closing session ${sessionId}`, error);
313
+ }
314
+ }
315
+ activeSessions.clear();
316
+ // Close database pool
317
+ await closePool();
318
+ // Close HTTP server
319
+ server.close(() => {
320
+ logger.info('HTTP server closed');
321
+ process.exit(0);
322
+ });
323
+ // Force exit after timeout
324
+ setTimeout(() => {
325
+ logger.warn('Forced shutdown after timeout');
326
+ process.exit(1);
327
+ }, 10000);
328
+ };
329
+ process.on('SIGINT', shutdown);
330
+ process.on('SIGTERM', shutdown);
331
+ }
332
+ // Start the server
333
+ main().catch((error) => {
334
+ logger.error('Fatal error during server startup', error);
335
+ process.exit(1);
336
+ });
337
+ //# sourceMappingURL=server.js.map