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.
- package/README.md +82 -2
- package/build/config/config.d.ts +6 -1
- package/build/config/config.js +24 -5
- package/build/config/config.js.map +1 -1
- package/build/db/index.d.ts +51 -0
- package/build/db/index.js +174 -0
- package/build/db/index.js.map +1 -0
- package/build/db/postgres-state-provider.d.ts +52 -0
- package/build/db/postgres-state-provider.js +165 -0
- package/build/db/postgres-state-provider.js.map +1 -0
- package/build/index.d.ts +4 -1
- package/build/index.js +17 -84
- package/build/index.js.map +1 -1
- package/build/mcp-server-factory.d.ts +36 -0
- package/build/mcp-server-factory.js +92 -0
- package/build/mcp-server-factory.js.map +1 -0
- package/build/server.d.ts +9 -0
- package/build/server.js +337 -0
- package/build/server.js.map +1 -0
- package/build/services/auth.service.d.ts +38 -0
- package/build/services/auth.service.js +64 -0
- package/build/services/auth.service.js.map +1 -1
- package/build/services/project.service.d.ts +11 -0
- package/build/services/project.service.js +31 -0
- package/build/services/project.service.js.map +1 -1
- package/build/services/state.service.d.ts +115 -0
- package/build/services/state.service.js +172 -0
- package/build/services/state.service.js.map +1 -0
- package/build/tools/create-connection.d.ts +12 -0
- package/build/tools/create-connection.js +6 -0
- package/build/tools/create-connection.js.map +1 -1
- package/build/tools/create-manual-investigation.js +10 -6
- package/build/tools/create-manual-investigation.js.map +1 -1
- package/build/tools/get-connection-info.js +1 -0
- package/build/tools/get-connection-info.js.map +1 -1
- package/build/tools/get-session-link.js +1 -2
- package/build/tools/get-session-link.js.map +1 -1
- package/build/tools/get-status.js +2 -4
- package/build/tools/get-status.js.map +1 -1
- package/build/tools/index.js +11 -1
- package/build/tools/index.js.map +1 -1
- package/build/tools/inspect-session.js +1 -2
- package/build/tools/inspect-session.js.map +1 -1
- package/build/tools/list-connection-types.js +9 -0
- package/build/tools/list-connection-types.js.map +1 -1
- package/build/types/hawkeye.d.ts +7 -0
- package/build/types/hawkeye.js.map +1 -1
- package/build/utils/connection-builders.d.ts +8 -0
- package/build/utils/connection-builders.js +48 -1
- package/build/utils/connection-builders.js.map +1 -1
- package/build/utils/state.d.ts +24 -17
- package/build/utils/state.js +63 -72
- package/build/utils/state.js.map +1 -1
- 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
|
-
//
|
|
21
|
-
import {
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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(`${
|
|
64
|
+
logger.info(`${toolCount} tools registered`);
|
|
132
65
|
}
|
|
133
66
|
catch (error) {
|
|
134
67
|
logger.error('Fatal error during server startup', error);
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA
|
|
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 {};
|
package/build/server.js
ADDED
|
@@ -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
|