hawkeye-mcp-server 2.2.0 → 2.2.1
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 +6 -0
- package/build/tools/create-connection.js +3 -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 +6 -0
- package/build/types/hawkeye.js.map +1 -1
- package/build/utils/connection-builders.d.ts +4 -0
- package/build/utils/connection-builders.js +24 -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
package/README.md
CHANGED
|
@@ -183,7 +183,20 @@ Works with any MCP-compatible client:
|
|
|
183
183
|
|
|
184
184
|
## 🔧 Configuration Options
|
|
185
185
|
|
|
186
|
-
###
|
|
186
|
+
### Transport Modes
|
|
187
|
+
|
|
188
|
+
Hawkeye MCP Server supports two transport modes:
|
|
189
|
+
|
|
190
|
+
| Mode | Use Case | Authentication |
|
|
191
|
+
|------|----------|----------------|
|
|
192
|
+
| **Stdio** (default) | npm/npx usage with MCP clients | Environment variables |
|
|
193
|
+
| **HTTP/SSE** | Kubernetes deployment, multi-user | Headers (Bearer token or password) |
|
|
194
|
+
|
|
195
|
+
### Stdio Transport (Default)
|
|
196
|
+
|
|
197
|
+
For use with Claude Desktop, Cursor, and other MCP clients:
|
|
198
|
+
|
|
199
|
+
#### Required Environment Variables
|
|
187
200
|
|
|
188
201
|
| Variable | Description |
|
|
189
202
|
|----------|-------------|
|
|
@@ -191,7 +204,7 @@ Works with any MCP-compatible client:
|
|
|
191
204
|
| `HAWKEYE_PASSWORD` | Your Hawkeye account password |
|
|
192
205
|
| `HAWKEYE_BASE_URL` | Hawkeye API endpoint (e.g., `https://app.neubird.ai/api`) |
|
|
193
206
|
|
|
194
|
-
|
|
207
|
+
#### Optional Environment Variables
|
|
195
208
|
|
|
196
209
|
| Variable | Default | Description |
|
|
197
210
|
|----------|---------|-------------|
|
|
@@ -200,6 +213,73 @@ Works with any MCP-compatible client:
|
|
|
200
213
|
| `HAWKEYE_MAX_POLL_ATTEMPTS` | `30` | Max polling attempts |
|
|
201
214
|
| `HAWKEYE_POLL_INTERVAL_MS` | `2000` | Polling interval (ms) |
|
|
202
215
|
|
|
216
|
+
### HTTP/SSE Transport (Kubernetes Deployment)
|
|
217
|
+
|
|
218
|
+
For multi-user deployments, use the HTTP/SSE transport with per-request authentication.
|
|
219
|
+
|
|
220
|
+
#### Server Configuration
|
|
221
|
+
|
|
222
|
+
| Variable | Description |
|
|
223
|
+
|----------|-------------|
|
|
224
|
+
| `TRANSPORT_MODE` | Set to `http` to enable HTTP/SSE transport |
|
|
225
|
+
| `HTTP_PORT` | HTTP server port (default: 3000) |
|
|
226
|
+
| `HAWKEYE_BASE_URL` | Hawkeye API endpoint |
|
|
227
|
+
| `CONFIGDB_URI` | PostgreSQL connection string |
|
|
228
|
+
| `CONFIGDB_USER` | PostgreSQL username |
|
|
229
|
+
| `CONFIGDB_PASSWORD` | PostgreSQL password |
|
|
230
|
+
|
|
231
|
+
#### Client Authentication
|
|
232
|
+
|
|
233
|
+
The HTTP/SSE transport supports two authentication methods:
|
|
234
|
+
|
|
235
|
+
**1. Bearer Token (Auth0/OAuth) - Recommended**
|
|
236
|
+
```
|
|
237
|
+
Authorization: Bearer <auth0_access_token>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**2. Password Credentials (Legacy)**
|
|
241
|
+
```
|
|
242
|
+
X-Hawkeye-Email: user@example.com
|
|
243
|
+
X-Hawkeye-Password: your-password
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### MCP Client Configuration (HTTP/SSE)
|
|
247
|
+
|
|
248
|
+
**With Bearer Token:**
|
|
249
|
+
```json
|
|
250
|
+
{
|
|
251
|
+
"mcpServers": {
|
|
252
|
+
"hawkeye": {
|
|
253
|
+
"url": "https://hawkeye-mcp.example.com/mcp",
|
|
254
|
+
"headers": {
|
|
255
|
+
"Authorization": "Bearer <auth0_access_token>"
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**With Password Credentials:**
|
|
263
|
+
```json
|
|
264
|
+
{
|
|
265
|
+
"mcpServers": {
|
|
266
|
+
"hawkeye": {
|
|
267
|
+
"url": "https://hawkeye-mcp.example.com/mcp",
|
|
268
|
+
"headers": {
|
|
269
|
+
"X-Hawkeye-Email": "user@example.com",
|
|
270
|
+
"X-Hawkeye-Password": "your-password"
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
#### Obtaining an Auth0 Token
|
|
278
|
+
|
|
279
|
+
Clients can obtain Auth0 tokens by:
|
|
280
|
+
1. **Via Hawkeye UI**: Login to hawkeye-ui, extract token from localStorage (`access_token`)
|
|
281
|
+
2. **Via Auth0 SDK**: Use Auth0 SDK to perform OAuth flow directly
|
|
282
|
+
|
|
203
283
|
## 👥 Contributing
|
|
204
284
|
|
|
205
285
|
We welcome contributions from NeuBird team members! This section is for internal developers.
|
package/build/config/config.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface HawkeyeConfig {
|
|
|
6
6
|
email: string;
|
|
7
7
|
password: string;
|
|
8
8
|
baseUrl: string;
|
|
9
|
+
uiBaseUrl: string;
|
|
9
10
|
defaultProjectUuid?: string;
|
|
10
11
|
defaultOrganizationUuid: string;
|
|
11
12
|
defaultTimeframe: string;
|
|
@@ -36,6 +37,10 @@ export declare function setBaseUrl(newBaseUrl: string): HawkeyeConfig;
|
|
|
36
37
|
* Get the current base URL
|
|
37
38
|
*/
|
|
38
39
|
export declare function getCurrentBaseUrl(): string;
|
|
40
|
+
/**
|
|
41
|
+
* Get the current UI base URL
|
|
42
|
+
*/
|
|
43
|
+
export declare function getCurrentUiBaseUrl(): string;
|
|
39
44
|
/**
|
|
40
45
|
* Constants used throughout the application
|
|
41
46
|
*/
|
|
@@ -59,7 +64,7 @@ export declare const CONSTANTS: {
|
|
|
59
64
|
readonly TIMEOUTS: {
|
|
60
65
|
readonly DEFAULT_REQUEST: 30000;
|
|
61
66
|
readonly LONG_REQUEST: 60000;
|
|
62
|
-
readonly AUTH_REQUEST:
|
|
67
|
+
readonly AUTH_REQUEST: 30000;
|
|
63
68
|
};
|
|
64
69
|
readonly RETRY: {
|
|
65
70
|
readonly MAX_ATTEMPTS: 3;
|
package/build/config/config.js
CHANGED
|
@@ -22,6 +22,7 @@ else {
|
|
|
22
22
|
console.error('[CONFIG] HAWKEYE_EMAIL:', process.env.HAWKEYE_EMAIL ? '✓ Set' : '✗ Not set');
|
|
23
23
|
console.error('[CONFIG] HAWKEYE_PASSWORD:', process.env.HAWKEYE_PASSWORD ? '✓ Set' : '✗ Not set');
|
|
24
24
|
console.error('[CONFIG] HAWKEYE_BASE_URL:', process.env.HAWKEYE_BASE_URL || 'Using default');
|
|
25
|
+
console.error('[CONFIG] HAWKEYE_UI_URL:', process.env.HAWKEYE_UI_URL || 'Using derived UI URL');
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
27
28
|
* Load and validate configuration from environment variables
|
|
@@ -29,7 +30,10 @@ else {
|
|
|
29
30
|
export function loadConfig() {
|
|
30
31
|
const email = process.env.HAWKEYE_EMAIL;
|
|
31
32
|
const password = process.env.HAWKEYE_PASSWORD;
|
|
32
|
-
|
|
33
|
+
const transportMode = process.env.TRANSPORT_MODE || 'stdio';
|
|
34
|
+
// In HTTP mode, credentials come from request headers, not environment
|
|
35
|
+
// In stdio mode, credentials are required from environment
|
|
36
|
+
if (transportMode === 'stdio' && (!email || !password)) {
|
|
33
37
|
throw new Error('Missing required environment variables: HAWKEYE_EMAIL and HAWKEYE_PASSWORD must be set');
|
|
34
38
|
}
|
|
35
39
|
// Priority for base URL: state file > env var > default
|
|
@@ -37,11 +41,14 @@ export function loadConfig() {
|
|
|
37
41
|
const baseUrl = stateManager.getBaseUrl() ||
|
|
38
42
|
process.env.HAWKEYE_BASE_URL ||
|
|
39
43
|
'https://sandbox.app.neubird.ai/api';
|
|
44
|
+
const uiBaseUrlRaw = process.env.HAWKEYE_UI_URL || process.env.HAWKEYE_BASE_URL || baseUrl;
|
|
45
|
+
const uiBaseUrl = uiBaseUrlRaw.replace(/\/api\/?$/, '');
|
|
40
46
|
return {
|
|
41
|
-
// Required
|
|
42
|
-
email,
|
|
43
|
-
password,
|
|
47
|
+
// Required (may be empty in HTTP mode where they come from headers)
|
|
48
|
+
email: email || '',
|
|
49
|
+
password: password || '',
|
|
44
50
|
baseUrl,
|
|
51
|
+
uiBaseUrl,
|
|
45
52
|
// Optional with defaults
|
|
46
53
|
defaultProjectUuid: process.env.HAWKEYE_DEFAULT_PROJECT_UUID,
|
|
47
54
|
defaultOrganizationUuid: process.env.HAWKEYE_DEFAULT_ORGANIZATION_UUID || 'ORGANIZATION_NAME_ROOT',
|
|
@@ -87,6 +94,12 @@ export function setBaseUrl(newBaseUrl) {
|
|
|
87
94
|
}
|
|
88
95
|
// Update the base URL
|
|
89
96
|
config.baseUrl = newBaseUrl;
|
|
97
|
+
if (process.env.HAWKEYE_UI_URL) {
|
|
98
|
+
config.uiBaseUrl = process.env.HAWKEYE_UI_URL.replace(/\/api\/?$/, '');
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
config.uiBaseUrl = newBaseUrl.replace(/\/api\/?$/, '');
|
|
102
|
+
}
|
|
90
103
|
return config;
|
|
91
104
|
}
|
|
92
105
|
/**
|
|
@@ -95,6 +108,12 @@ export function setBaseUrl(newBaseUrl) {
|
|
|
95
108
|
export function getCurrentBaseUrl() {
|
|
96
109
|
return getConfig().baseUrl;
|
|
97
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Get the current UI base URL
|
|
113
|
+
*/
|
|
114
|
+
export function getCurrentUiBaseUrl() {
|
|
115
|
+
return getConfig().uiBaseUrl;
|
|
116
|
+
}
|
|
98
117
|
/**
|
|
99
118
|
* Constants used throughout the application
|
|
100
119
|
*/
|
|
@@ -121,7 +140,7 @@ export const CONSTANTS = {
|
|
|
121
140
|
TIMEOUTS: {
|
|
122
141
|
DEFAULT_REQUEST: 30000, // 30 seconds
|
|
123
142
|
LONG_REQUEST: 60000, // 1 minute
|
|
124
|
-
AUTH_REQUEST:
|
|
143
|
+
AUTH_REQUEST: 30000, // 30 seconds
|
|
125
144
|
},
|
|
126
145
|
// Retry configuration
|
|
127
146
|
RETRY: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,kCAAkC;AAClC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,+CAA+C;AAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;AAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AAEhD,6CAA6C;AAC7C,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC1E,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAC;AACjD,CAAC;KAAM,CAAC;IACN,OAAO,CAAC,KAAK,CAAC,yCAAyC,EAAE,OAAO,CAAC,CAAC;IAClE,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IAC5F,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IAClG,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,eAAe,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL database connection module
|
|
3
|
+
* Provides connection pooling and health check functionality
|
|
4
|
+
*/
|
|
5
|
+
import pg from 'pg';
|
|
6
|
+
/**
|
|
7
|
+
* Database configuration from environment variables
|
|
8
|
+
*/
|
|
9
|
+
export interface DatabaseConfig {
|
|
10
|
+
uri: string;
|
|
11
|
+
user: string;
|
|
12
|
+
password: string;
|
|
13
|
+
maxConnections?: number;
|
|
14
|
+
idleTimeoutMs?: number;
|
|
15
|
+
connectionTimeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get database configuration from environment variables
|
|
19
|
+
*/
|
|
20
|
+
export declare function getDatabaseConfig(): DatabaseConfig;
|
|
21
|
+
/**
|
|
22
|
+
* Initialize the database connection pool
|
|
23
|
+
* @param config - Optional database configuration (defaults to environment variables)
|
|
24
|
+
*/
|
|
25
|
+
export declare function initializePool(config?: DatabaseConfig): pg.Pool;
|
|
26
|
+
/**
|
|
27
|
+
* Get the database connection pool
|
|
28
|
+
* Initializes the pool if not already done
|
|
29
|
+
*/
|
|
30
|
+
export declare function getPool(): pg.Pool;
|
|
31
|
+
/**
|
|
32
|
+
* Check if the database connection is healthy
|
|
33
|
+
* @returns true if the database is reachable, false otherwise
|
|
34
|
+
*/
|
|
35
|
+
export declare function checkDatabaseHealth(): Promise<boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* Close the database connection pool
|
|
38
|
+
* Should be called when shutting down the server
|
|
39
|
+
*/
|
|
40
|
+
export declare function closePool(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Execute a query with automatic connection management
|
|
43
|
+
* @param text - SQL query text
|
|
44
|
+
* @param params - Query parameters
|
|
45
|
+
*/
|
|
46
|
+
export declare function query<T extends pg.QueryResultRow = any>(text: string, params?: any[]): Promise<pg.QueryResult<T>>;
|
|
47
|
+
/**
|
|
48
|
+
* Run database migrations
|
|
49
|
+
* Creates necessary tables if they don't exist
|
|
50
|
+
*/
|
|
51
|
+
export declare function runMigrations(): Promise<void>;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL database connection module
|
|
3
|
+
* Provides connection pooling and health check functionality
|
|
4
|
+
*/
|
|
5
|
+
import pg from 'pg';
|
|
6
|
+
import { logger } from '../utils/logger.js';
|
|
7
|
+
const { Pool } = pg;
|
|
8
|
+
/**
|
|
9
|
+
* Get database configuration from environment variables
|
|
10
|
+
*/
|
|
11
|
+
export function getDatabaseConfig() {
|
|
12
|
+
const uri = process.env.CONFIGDB_URI;
|
|
13
|
+
const user = process.env.CONFIGDB_USER;
|
|
14
|
+
const password = process.env.CONFIGDB_PASSWORD;
|
|
15
|
+
if (!uri || !user || !password) {
|
|
16
|
+
throw new Error('Missing required database environment variables. ' +
|
|
17
|
+
'Please set CONFIGDB_URI, CONFIGDB_USER, and CONFIGDB_PASSWORD.');
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
uri,
|
|
21
|
+
user,
|
|
22
|
+
password,
|
|
23
|
+
maxConnections: parseInt(process.env.CONFIGDB_MAX_CONNECTIONS || '10', 10),
|
|
24
|
+
idleTimeoutMs: parseInt(process.env.CONFIGDB_IDLE_TIMEOUT_MS || '30000', 10),
|
|
25
|
+
connectionTimeoutMs: parseInt(process.env.CONFIGDB_CONNECTION_TIMEOUT_MS || '5000', 10),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* PostgreSQL connection pool singleton
|
|
30
|
+
*/
|
|
31
|
+
let pool = null;
|
|
32
|
+
/**
|
|
33
|
+
* Initialize the database connection pool
|
|
34
|
+
* @param config - Optional database configuration (defaults to environment variables)
|
|
35
|
+
*/
|
|
36
|
+
export function initializePool(config) {
|
|
37
|
+
if (pool) {
|
|
38
|
+
return pool;
|
|
39
|
+
}
|
|
40
|
+
const dbConfig = config || getDatabaseConfig();
|
|
41
|
+
// Parse the URI to extract host, port, and database
|
|
42
|
+
const url = new URL(dbConfig.uri);
|
|
43
|
+
// Determine SSL mode - disable for localhost, enable for remote
|
|
44
|
+
const isLocalhost = url.hostname === 'localhost' || url.hostname === '127.0.0.1';
|
|
45
|
+
const sslEnabled = process.env.CONFIGDB_SSL !== 'false' && !isLocalhost;
|
|
46
|
+
pool = new Pool({
|
|
47
|
+
host: url.hostname,
|
|
48
|
+
port: parseInt(url.port || '5432', 10),
|
|
49
|
+
database: url.pathname.slice(1), // Remove leading '/'
|
|
50
|
+
user: dbConfig.user,
|
|
51
|
+
password: dbConfig.password,
|
|
52
|
+
max: dbConfig.maxConnections,
|
|
53
|
+
idleTimeoutMillis: dbConfig.idleTimeoutMs,
|
|
54
|
+
connectionTimeoutMillis: dbConfig.connectionTimeoutMs,
|
|
55
|
+
ssl: sslEnabled ? { rejectUnauthorized: false } : false,
|
|
56
|
+
});
|
|
57
|
+
// Log pool events
|
|
58
|
+
pool.on('connect', () => {
|
|
59
|
+
logger.debug('New database client connected');
|
|
60
|
+
});
|
|
61
|
+
pool.on('error', (err) => {
|
|
62
|
+
logger.error('Unexpected database pool error', { error: err.message });
|
|
63
|
+
});
|
|
64
|
+
logger.info('Database connection pool initialized', {
|
|
65
|
+
host: url.hostname,
|
|
66
|
+
port: url.port,
|
|
67
|
+
database: url.pathname.slice(1),
|
|
68
|
+
maxConnections: dbConfig.maxConnections,
|
|
69
|
+
});
|
|
70
|
+
return pool;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get the database connection pool
|
|
74
|
+
* Initializes the pool if not already done
|
|
75
|
+
*/
|
|
76
|
+
export function getPool() {
|
|
77
|
+
if (!pool) {
|
|
78
|
+
return initializePool();
|
|
79
|
+
}
|
|
80
|
+
return pool;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if the database connection is healthy
|
|
84
|
+
* @returns true if the database is reachable, false otherwise
|
|
85
|
+
*/
|
|
86
|
+
export async function checkDatabaseHealth() {
|
|
87
|
+
try {
|
|
88
|
+
const dbPool = getPool();
|
|
89
|
+
const result = await dbPool.query('SELECT 1 as health');
|
|
90
|
+
return result.rows.length > 0 && result.rows[0].health === 1;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
logger.error('Database health check failed', {
|
|
94
|
+
error: error instanceof Error ? error.message : String(error),
|
|
95
|
+
});
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Close the database connection pool
|
|
101
|
+
* Should be called when shutting down the server
|
|
102
|
+
*/
|
|
103
|
+
export async function closePool() {
|
|
104
|
+
if (pool) {
|
|
105
|
+
logger.info('Closing database connection pool');
|
|
106
|
+
await pool.end();
|
|
107
|
+
pool = null;
|
|
108
|
+
logger.info('Database connection pool closed');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Execute a query with automatic connection management
|
|
113
|
+
* @param text - SQL query text
|
|
114
|
+
* @param params - Query parameters
|
|
115
|
+
*/
|
|
116
|
+
export async function query(text, params) {
|
|
117
|
+
const dbPool = getPool();
|
|
118
|
+
const start = Date.now();
|
|
119
|
+
try {
|
|
120
|
+
const result = await dbPool.query(text, params);
|
|
121
|
+
const duration = Date.now() - start;
|
|
122
|
+
logger.debug('Executed query', {
|
|
123
|
+
text: text.substring(0, 100), // Truncate for logging
|
|
124
|
+
duration,
|
|
125
|
+
rows: result.rowCount,
|
|
126
|
+
});
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
logger.error('Query execution failed', {
|
|
131
|
+
text: text.substring(0, 100),
|
|
132
|
+
error: error instanceof Error ? error.message : String(error),
|
|
133
|
+
});
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Run database migrations
|
|
139
|
+
* Creates necessary tables if they don't exist
|
|
140
|
+
*/
|
|
141
|
+
export async function runMigrations() {
|
|
142
|
+
logger.info('Running database migrations');
|
|
143
|
+
const migrations = [
|
|
144
|
+
// Migration 001: Create mcp_user_state table
|
|
145
|
+
`
|
|
146
|
+
CREATE TABLE IF NOT EXISTS mcp_user_state (
|
|
147
|
+
id SERIAL PRIMARY KEY,
|
|
148
|
+
user_email VARCHAR(255) NOT NULL UNIQUE,
|
|
149
|
+
default_project_uuid VARCHAR(255),
|
|
150
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
151
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
152
|
+
);
|
|
153
|
+
`,
|
|
154
|
+
// Create index on user_email for faster lookups
|
|
155
|
+
`
|
|
156
|
+
CREATE INDEX IF NOT EXISTS idx_mcp_user_state_email
|
|
157
|
+
ON mcp_user_state(user_email);
|
|
158
|
+
`,
|
|
159
|
+
];
|
|
160
|
+
for (const migration of migrations) {
|
|
161
|
+
try {
|
|
162
|
+
await query(migration);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
// Ignore "already exists" errors
|
|
166
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
167
|
+
if (!message.includes('already exists')) {
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
logger.info('Database migrations completed');
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL-backed state provider for HTTP/SSE server mode
|
|
3
|
+
* Stores user state in the configdb database, keyed by user email
|
|
4
|
+
*/
|
|
5
|
+
import { StateProvider, StateData } from '../services/state.service.js';
|
|
6
|
+
/**
|
|
7
|
+
* PostgreSQL state provider for multi-user server deployments
|
|
8
|
+
* Each user's state is stored in the mcp_user_state table, keyed by email
|
|
9
|
+
*/
|
|
10
|
+
export declare class PostgresStateProvider implements StateProvider {
|
|
11
|
+
/**
|
|
12
|
+
* Get the default project UUID for a user
|
|
13
|
+
*/
|
|
14
|
+
getDefaultProjectUuid(userIdentifier?: string): Promise<string | undefined>;
|
|
15
|
+
/**
|
|
16
|
+
* Set the default project UUID for a user
|
|
17
|
+
* Uses upsert (INSERT ... ON CONFLICT) to create or update
|
|
18
|
+
*/
|
|
19
|
+
setDefaultProjectUuid(projectUuid: string, userIdentifier?: string): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Clear the default project UUID for a user
|
|
22
|
+
*/
|
|
23
|
+
clearDefaultProjectUuid(userIdentifier?: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Get the base URL
|
|
26
|
+
* Note: In HTTP/SSE mode, base URL is fixed per deployment (from env var)
|
|
27
|
+
* This method always returns undefined - base URL switching is not supported
|
|
28
|
+
*/
|
|
29
|
+
getBaseUrl(_userIdentifier?: string): Promise<string | undefined>;
|
|
30
|
+
/**
|
|
31
|
+
* Set the base URL
|
|
32
|
+
* Note: Not supported in HTTP/SSE mode - base URL is fixed per deployment
|
|
33
|
+
*/
|
|
34
|
+
setBaseUrl(_baseUrl: string, _userIdentifier?: string): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Clear the base URL
|
|
37
|
+
* Note: Not supported in HTTP/SSE mode
|
|
38
|
+
*/
|
|
39
|
+
clearBaseUrl(_userIdentifier?: string): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Clear all state for a user
|
|
42
|
+
*/
|
|
43
|
+
clearAll(userIdentifier?: string): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Get all state data for a user
|
|
46
|
+
*/
|
|
47
|
+
getAll(userIdentifier?: string): Promise<StateData>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get the PostgreSQL state provider singleton
|
|
51
|
+
*/
|
|
52
|
+
export declare function getPostgresStateProvider(): PostgresStateProvider;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL-backed state provider for HTTP/SSE server mode
|
|
3
|
+
* Stores user state in the configdb database, keyed by user email
|
|
4
|
+
*/
|
|
5
|
+
import { query } from './index.js';
|
|
6
|
+
import { logger } from '../utils/logger.js';
|
|
7
|
+
/**
|
|
8
|
+
* PostgreSQL state provider for multi-user server deployments
|
|
9
|
+
* Each user's state is stored in the mcp_user_state table, keyed by email
|
|
10
|
+
*/
|
|
11
|
+
export class PostgresStateProvider {
|
|
12
|
+
/**
|
|
13
|
+
* Get the default project UUID for a user
|
|
14
|
+
*/
|
|
15
|
+
async getDefaultProjectUuid(userIdentifier) {
|
|
16
|
+
if (!userIdentifier) {
|
|
17
|
+
logger.warn('PostgresStateProvider.getDefaultProjectUuid called without userIdentifier');
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const result = await query('SELECT default_project_uuid FROM mcp_user_state WHERE user_email = $1', [userIdentifier]);
|
|
22
|
+
if (result.rows.length === 0) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
return result.rows[0].default_project_uuid || undefined;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
logger.error('Failed to get default project UUID from database', {
|
|
29
|
+
userIdentifier,
|
|
30
|
+
error: error instanceof Error ? error.message : String(error),
|
|
31
|
+
});
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Set the default project UUID for a user
|
|
37
|
+
* Uses upsert (INSERT ... ON CONFLICT) to create or update
|
|
38
|
+
*/
|
|
39
|
+
async setDefaultProjectUuid(projectUuid, userIdentifier) {
|
|
40
|
+
if (!userIdentifier) {
|
|
41
|
+
throw new Error('PostgresStateProvider.setDefaultProjectUuid requires userIdentifier');
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
await query(`INSERT INTO mcp_user_state (user_email, default_project_uuid, updated_at)
|
|
45
|
+
VALUES ($1, $2, NOW())
|
|
46
|
+
ON CONFLICT (user_email)
|
|
47
|
+
DO UPDATE SET default_project_uuid = $2, updated_at = NOW()`, [userIdentifier, projectUuid]);
|
|
48
|
+
logger.info('Set default project UUID in database', { userIdentifier, projectUuid });
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
logger.error('Failed to set default project UUID in database', {
|
|
52
|
+
userIdentifier,
|
|
53
|
+
projectUuid,
|
|
54
|
+
error: error instanceof Error ? error.message : String(error),
|
|
55
|
+
});
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Clear the default project UUID for a user
|
|
61
|
+
*/
|
|
62
|
+
async clearDefaultProjectUuid(userIdentifier) {
|
|
63
|
+
if (!userIdentifier) {
|
|
64
|
+
throw new Error('PostgresStateProvider.clearDefaultProjectUuid requires userIdentifier');
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
await query(`UPDATE mcp_user_state
|
|
68
|
+
SET default_project_uuid = NULL, updated_at = NOW()
|
|
69
|
+
WHERE user_email = $1`, [userIdentifier]);
|
|
70
|
+
logger.info('Cleared default project UUID in database', { userIdentifier });
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
logger.error('Failed to clear default project UUID in database', {
|
|
74
|
+
userIdentifier,
|
|
75
|
+
error: error instanceof Error ? error.message : String(error),
|
|
76
|
+
});
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get the base URL
|
|
82
|
+
* Note: In HTTP/SSE mode, base URL is fixed per deployment (from env var)
|
|
83
|
+
* This method always returns undefined - base URL switching is not supported
|
|
84
|
+
*/
|
|
85
|
+
async getBaseUrl(_userIdentifier) {
|
|
86
|
+
// In HTTP/SSE mode, base URL is set via environment variable
|
|
87
|
+
// Instance switching is not supported (each deployment serves one Hawkeye instance)
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Set the base URL
|
|
92
|
+
* Note: Not supported in HTTP/SSE mode - base URL is fixed per deployment
|
|
93
|
+
*/
|
|
94
|
+
async setBaseUrl(_baseUrl, _userIdentifier) {
|
|
95
|
+
logger.warn('PostgresStateProvider.setBaseUrl called but base URL switching is not supported in HTTP/SSE mode. ' +
|
|
96
|
+
'The base URL is fixed per deployment via HAWKEYE_BASE_URL environment variable.');
|
|
97
|
+
// No-op - base URL is fixed in server mode
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Clear the base URL
|
|
101
|
+
* Note: Not supported in HTTP/SSE mode
|
|
102
|
+
*/
|
|
103
|
+
async clearBaseUrl(_userIdentifier) {
|
|
104
|
+
// No-op - base URL is fixed in server mode
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Clear all state for a user
|
|
108
|
+
*/
|
|
109
|
+
async clearAll(userIdentifier) {
|
|
110
|
+
if (!userIdentifier) {
|
|
111
|
+
throw new Error('PostgresStateProvider.clearAll requires userIdentifier');
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
await query('DELETE FROM mcp_user_state WHERE user_email = $1', [userIdentifier]);
|
|
115
|
+
logger.info('Cleared all state in database', { userIdentifier });
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
logger.error('Failed to clear all state in database', {
|
|
119
|
+
userIdentifier,
|
|
120
|
+
error: error instanceof Error ? error.message : String(error),
|
|
121
|
+
});
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get all state data for a user
|
|
127
|
+
*/
|
|
128
|
+
async getAll(userIdentifier) {
|
|
129
|
+
if (!userIdentifier) {
|
|
130
|
+
return {};
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const result = await query('SELECT default_project_uuid FROM mcp_user_state WHERE user_email = $1', [userIdentifier]);
|
|
134
|
+
if (result.rows.length === 0) {
|
|
135
|
+
return {};
|
|
136
|
+
}
|
|
137
|
+
const row = result.rows[0];
|
|
138
|
+
return {
|
|
139
|
+
defaultProjectUuid: row.default_project_uuid || undefined,
|
|
140
|
+
// baseUrl is not stored in database (fixed per deployment)
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
logger.error('Failed to get all state from database', {
|
|
145
|
+
userIdentifier,
|
|
146
|
+
error: error instanceof Error ? error.message : String(error),
|
|
147
|
+
});
|
|
148
|
+
return {};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Singleton instance for PostgreSQL state provider
|
|
154
|
+
*/
|
|
155
|
+
let postgresStateProvider = null;
|
|
156
|
+
/**
|
|
157
|
+
* Get the PostgreSQL state provider singleton
|
|
158
|
+
*/
|
|
159
|
+
export function getPostgresStateProvider() {
|
|
160
|
+
if (!postgresStateProvider) {
|
|
161
|
+
postgresStateProvider = new PostgresStateProvider();
|
|
162
|
+
}
|
|
163
|
+
return postgresStateProvider;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=postgres-state-provider.js.map
|