@mcp-shark/mcp-shark 1.5.3 → 1.5.5
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 +32 -96
- package/bin/mcp-shark.js +1 -1
- package/core/configs/codex.js +68 -0
- package/core/configs/environment.js +51 -0
- package/{lib/common → core}/configs/index.js +16 -1
- package/core/constants/Defaults.js +15 -0
- package/core/constants/HttpStatus.js +14 -0
- package/core/constants/Server.js +20 -0
- package/core/constants/StatusCodes.js +25 -0
- package/core/constants/index.js +7 -0
- package/core/container/DependencyContainer.js +179 -0
- package/core/db/init.js +33 -0
- package/core/index.js +10 -0
- package/{mcp-server/lib/common/error.js → core/libraries/ErrorLibrary.js} +4 -0
- package/core/libraries/LoggerLibrary.js +91 -0
- package/core/libraries/SerializationLibrary.js +32 -0
- package/core/libraries/bootstrap-logger.js +19 -0
- package/core/libraries/errors/ApplicationError.js +97 -0
- package/core/libraries/index.js +17 -0
- package/{mcp-server/lib → core/mcp-server}/auditor/audit.js +77 -53
- package/core/mcp-server/index.js +192 -0
- package/{mcp-server/lib → core/mcp-server}/server/external/all.js +1 -1
- package/core/mcp-server/server/external/config.js +75 -0
- package/{mcp-server/lib → core/mcp-server}/server/external/single/client.js +1 -1
- package/{mcp-server/lib → core/mcp-server}/server/external/single/request.js +1 -1
- package/{mcp-server/lib → core/mcp-server}/server/external/single/run.js +20 -11
- package/{mcp-server/lib → core/mcp-server}/server/external/single/transport.js +1 -1
- package/{mcp-server/lib → core/mcp-server}/server/internal/handlers/error.js +1 -1
- package/core/mcp-server/server/internal/handlers/prompts-get.js +28 -0
- package/core/mcp-server/server/internal/handlers/prompts-list.js +21 -0
- package/core/mcp-server/server/internal/handlers/resources-list.js +21 -0
- package/core/mcp-server/server/internal/handlers/resources-read.js +28 -0
- package/core/mcp-server/server/internal/handlers/tools-call.js +44 -0
- package/core/mcp-server/server/internal/handlers/tools-list.js +23 -0
- package/core/mcp-server/server/internal/run.js +53 -0
- package/{mcp-server/lib → core/mcp-server}/server/internal/server.js +11 -1
- package/core/models/ConversationFilters.js +31 -0
- package/core/models/ExportFormat.js +8 -0
- package/core/models/RequestFilters.js +43 -0
- package/core/models/SessionFilters.js +23 -0
- package/core/models/index.js +8 -0
- package/core/repositories/AuditRepository.js +233 -0
- package/core/repositories/ConversationRepository.js +182 -0
- package/core/repositories/PacketRepository.js +237 -0
- package/core/repositories/SchemaRepository.js +107 -0
- package/core/repositories/SessionRepository.js +59 -0
- package/core/repositories/StatisticsRepository.js +54 -0
- package/core/repositories/index.js +10 -0
- package/core/services/AuditService.js +144 -0
- package/core/services/BackupService.js +222 -0
- package/core/services/ConfigDetectionService.js +89 -0
- package/core/services/ConfigFileService.js +210 -0
- package/core/services/ConfigPatchingService.js +137 -0
- package/core/services/ConfigService.js +250 -0
- package/core/services/ConfigTransformService.js +178 -0
- package/core/services/ConversationService.js +19 -0
- package/core/services/ExportService.js +117 -0
- package/core/services/LogService.js +64 -0
- package/core/services/McpClientService.js +235 -0
- package/core/services/McpDiscoveryService.js +107 -0
- package/core/services/RequestService.js +56 -0
- package/core/services/ScanCacheService.js +242 -0
- package/core/services/ScanService.js +167 -0
- package/core/services/ServerManagementService.js +206 -0
- package/core/services/SessionService.js +34 -0
- package/core/services/SettingsService.js +163 -0
- package/core/services/StatisticsService.js +64 -0
- package/core/services/TokenService.js +94 -0
- package/core/services/index.js +25 -0
- package/core/services/parsers/ConfigParserFactory.js +113 -0
- package/core/services/parsers/JsonConfigParser.js +66 -0
- package/core/services/parsers/LegacyJsonConfigParser.js +71 -0
- package/core/services/parsers/TomlConfigParser.js +87 -0
- package/core/services/parsers/index.js +4 -0
- package/{ui/server → core}/utils/scan-cache/directory.js +1 -1
- package/core/utils/validation.js +77 -0
- package/package.json +14 -11
- package/ui/dist/assets/index-CArYxKxS.js +35 -0
- package/ui/dist/index.html +1 -1
- package/ui/server/controllers/BackupController.js +129 -0
- package/ui/server/controllers/ConfigController.js +92 -0
- package/ui/server/controllers/ConversationController.js +41 -0
- package/ui/server/controllers/LogController.js +44 -0
- package/ui/server/controllers/McpClientController.js +60 -0
- package/ui/server/controllers/McpDiscoveryController.js +44 -0
- package/ui/server/controllers/RequestController.js +129 -0
- package/ui/server/controllers/ScanController.js +122 -0
- package/ui/server/controllers/ServerManagementController.js +134 -0
- package/ui/server/controllers/SessionController.js +57 -0
- package/ui/server/controllers/SettingsController.js +24 -0
- package/ui/server/controllers/StatisticsController.js +54 -0
- package/ui/server/controllers/TokenController.js +58 -0
- package/ui/server/controllers/index.js +17 -0
- package/ui/server/routes/backups/index.js +15 -9
- package/ui/server/routes/composite/index.js +62 -32
- package/ui/server/routes/composite/servers.js +20 -15
- package/ui/server/routes/config.js +13 -172
- package/ui/server/routes/conversations.js +9 -19
- package/ui/server/routes/help.js +4 -3
- package/ui/server/routes/logs.js +14 -26
- package/ui/server/routes/playground.js +11 -174
- package/ui/server/routes/requests.js +12 -232
- package/ui/server/routes/sessions.js +10 -21
- package/ui/server/routes/settings.js +10 -192
- package/ui/server/routes/smartscan.js +26 -15
- package/ui/server/routes/statistics.js +8 -79
- package/ui/server/setup.js +162 -0
- package/ui/server/swagger/paths/backups.js +151 -0
- package/ui/server/swagger/paths/components.js +76 -0
- package/ui/server/swagger/paths/config.js +117 -0
- package/ui/server/swagger/paths/conversations.js +29 -0
- package/ui/server/swagger/paths/help.js +82 -0
- package/ui/server/swagger/paths/logs.js +87 -0
- package/ui/server/swagger/paths/playground.js +49 -0
- package/ui/server/swagger/paths/requests.js +178 -0
- package/ui/server/swagger/paths/serverManagement.js +169 -0
- package/ui/server/swagger/paths/sessions.js +61 -0
- package/ui/server/swagger/paths/settings.js +31 -0
- package/ui/server/swagger/paths/smartScan/discovery.js +97 -0
- package/ui/server/swagger/paths/smartScan/index.js +13 -0
- package/ui/server/swagger/paths/smartScan/scans.js +151 -0
- package/ui/server/swagger/paths/smartScan/token.js +71 -0
- package/ui/server/swagger/paths/statistics.js +40 -0
- package/ui/server/swagger/paths.js +38 -0
- package/ui/server/swagger/swagger.js +37 -0
- package/ui/server/utils/cleanup.js +99 -0
- package/ui/server/utils/config.js +18 -96
- package/ui/server/utils/errorHandler.js +43 -0
- package/ui/server/utils/logger.js +2 -2
- package/ui/server/utils/paths.js +27 -30
- package/ui/server/utils/port.js +21 -21
- package/ui/server/utils/process.js +18 -10
- package/ui/server/utils/processState.js +17 -0
- package/ui/server/utils/signals.js +34 -0
- package/ui/server/websocket/broadcast.js +33 -0
- package/ui/server/websocket/handler.js +52 -0
- package/ui/server.js +51 -230
- package/ui/src/App.jsx +2 -0
- package/ui/src/CompositeSetup.jsx +23 -9
- package/ui/src/PacketFilters.jsx +17 -3
- package/ui/src/components/AlertModal.jsx +116 -0
- package/ui/src/components/App/ApiDocsButton.jsx +57 -0
- package/ui/src/components/App/useAppState.js +43 -1
- package/ui/src/components/BackupList.jsx +27 -3
- package/ui/src/utils/requestPairing.js +35 -36
- package/ui/src/utils/requestUtils.js +1 -0
- package/lib/common/db/init.js +0 -132
- package/lib/common/db/logger.js +0 -349
- package/lib/common/db/query.js +0 -403
- package/lib/common/logger.js +0 -90
- package/mcp-server/index.js +0 -111
- package/mcp-server/lib/server/external/config.js +0 -57
- package/mcp-server/lib/server/internal/handlers/prompts-get.js +0 -20
- package/mcp-server/lib/server/internal/handlers/prompts-list.js +0 -13
- package/mcp-server/lib/server/internal/handlers/resources-list.js +0 -13
- package/mcp-server/lib/server/internal/handlers/resources-read.js +0 -20
- package/mcp-server/lib/server/internal/handlers/tools-call.js +0 -35
- package/mcp-server/lib/server/internal/handlers/tools-list.js +0 -15
- package/mcp-server/lib/server/internal/run.js +0 -37
- package/mcp-server/mcp-shark.js +0 -22
- package/ui/dist/assets/index-CFHeMNwd.js +0 -35
- package/ui/server/routes/backups/deleteBackup.js +0 -54
- package/ui/server/routes/backups/listBackups.js +0 -75
- package/ui/server/routes/backups/restoreBackup.js +0 -83
- package/ui/server/routes/backups/viewBackup.js +0 -47
- package/ui/server/routes/composite/setup.js +0 -129
- package/ui/server/routes/composite/status.js +0 -7
- package/ui/server/routes/composite/stop.js +0 -39
- package/ui/server/routes/composite/utils.js +0 -45
- package/ui/server/routes/smartscan/discover.js +0 -118
- package/ui/server/routes/smartscan/scans/clearCache.js +0 -23
- package/ui/server/routes/smartscan/scans/createBatchScans.js +0 -124
- package/ui/server/routes/smartscan/scans/createScan.js +0 -43
- package/ui/server/routes/smartscan/scans/getCachedResults.js +0 -52
- package/ui/server/routes/smartscan/scans/getScan.js +0 -42
- package/ui/server/routes/smartscan/scans/listScans.js +0 -25
- package/ui/server/routes/smartscan/scans.js +0 -13
- package/ui/server/routes/smartscan/token.js +0 -57
- package/ui/server/utils/config-update.js +0 -240
- package/ui/server/utils/scan-cache/all-results.js +0 -197
- package/ui/server/utils/scan-cache/file-operations.js +0 -107
- package/ui/server/utils/scan-cache/hash.js +0 -47
- package/ui/server/utils/scan-cache/server-operations.js +0 -85
- package/ui/server/utils/scan-cache.js +0 -12
- package/ui/server/utils/smartscan-token.js +0 -43
- /package/{mcp-server/lib → core/mcp-server}/server/external/kv.js +0 -0
- /package/{mcp-server/lib → core/mcp-server}/server/internal/handlers/common.js +0 -0
- /package/{mcp-server/lib → core/mcp-server}/server/internal/session.js +0 -0
package/ui/dist/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<title>MCP Shark</title>
|
|
8
8
|
<link rel="icon" type="image/png" href="/og-image.png" />
|
|
9
9
|
<link rel="apple-touch-icon" href="/og-image.png" />
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-CArYxKxS.js"></script>
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/assets/index-Cc-IUa83.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { StatusCodes } from '#core/constants/index.js';
|
|
2
|
+
import { NotFoundError } from '#core/libraries/index.js';
|
|
3
|
+
import { handleError, handleValidationError } from '../utils/errorHandler.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Controller for backup-related HTTP endpoints
|
|
7
|
+
*/
|
|
8
|
+
export class BackupController {
|
|
9
|
+
constructor(backupService, serverManagementService, logger) {
|
|
10
|
+
this.backupService = backupService;
|
|
11
|
+
this.serverManagementService = serverManagementService;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get restore message based on result
|
|
17
|
+
* @private
|
|
18
|
+
*/
|
|
19
|
+
_getRestoreMessage(result) {
|
|
20
|
+
if (result.wasPatched && result.repatched) {
|
|
21
|
+
return 'Config file restored from backup and automatically repatched (server is running)';
|
|
22
|
+
}
|
|
23
|
+
if (result.wasPatched && !result.repatched) {
|
|
24
|
+
return 'Config file restored from backup (was patched, but server is not running)';
|
|
25
|
+
}
|
|
26
|
+
return 'Config file restored from backup';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
listBackups = (_req, res) => {
|
|
30
|
+
try {
|
|
31
|
+
const backups = this.backupService.listBackups();
|
|
32
|
+
res.json({ backups });
|
|
33
|
+
} catch (error) {
|
|
34
|
+
handleError(error, res, this.logger, 'Error listing backups');
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
viewBackup = (req, res) => {
|
|
39
|
+
try {
|
|
40
|
+
const { backupPath } = req.query;
|
|
41
|
+
|
|
42
|
+
if (!backupPath) {
|
|
43
|
+
return handleValidationError('backupPath is required', res, this.logger);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = this.backupService.viewBackup(backupPath);
|
|
47
|
+
|
|
48
|
+
if (!result) {
|
|
49
|
+
return handleError(
|
|
50
|
+
new NotFoundError('Backup file not found', null),
|
|
51
|
+
res,
|
|
52
|
+
this.logger,
|
|
53
|
+
'Error viewing backup'
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
res.json({
|
|
58
|
+
success: true,
|
|
59
|
+
...result,
|
|
60
|
+
});
|
|
61
|
+
} catch (error) {
|
|
62
|
+
handleError(error, res, this.logger, 'Error viewing backup');
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
restoreBackup = (req, res) => {
|
|
67
|
+
try {
|
|
68
|
+
const { backupPath, originalPath } = req.body;
|
|
69
|
+
|
|
70
|
+
if (!backupPath) {
|
|
71
|
+
return handleValidationError('backupPath is required', res, this.logger);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check if server is running
|
|
75
|
+
const serverStatus = this.serverManagementService.getServerStatus();
|
|
76
|
+
const serverIsRunning = serverStatus.running;
|
|
77
|
+
|
|
78
|
+
const result = this.backupService.restoreBackup(backupPath, originalPath, serverIsRunning);
|
|
79
|
+
|
|
80
|
+
if (!result.success) {
|
|
81
|
+
return res.status(StatusCodes.BAD_REQUEST).json({
|
|
82
|
+
error: 'ValidationError',
|
|
83
|
+
message: result.error || 'Failed to restore backup',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const message = this._getRestoreMessage(result);
|
|
88
|
+
|
|
89
|
+
res.json({
|
|
90
|
+
success: true,
|
|
91
|
+
message,
|
|
92
|
+
originalPath: result.originalPath,
|
|
93
|
+
wasPatched: result.wasPatched,
|
|
94
|
+
repatched: result.repatched,
|
|
95
|
+
});
|
|
96
|
+
} catch (error) {
|
|
97
|
+
handleError(error, res, this.logger, 'Error restoring backup');
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
deleteBackup = (req, res) => {
|
|
102
|
+
try {
|
|
103
|
+
const { backupPath } = req.body;
|
|
104
|
+
|
|
105
|
+
if (!backupPath) {
|
|
106
|
+
return handleValidationError('backupPath is required', res, this.logger);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const result = this.backupService.deleteBackup(backupPath);
|
|
110
|
+
|
|
111
|
+
if (!result.success) {
|
|
112
|
+
return handleError(
|
|
113
|
+
new NotFoundError(result.error || 'Backup file not found', null),
|
|
114
|
+
res,
|
|
115
|
+
this.logger,
|
|
116
|
+
'Error deleting backup'
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
res.json({
|
|
121
|
+
success: true,
|
|
122
|
+
message: 'Backup file deleted successfully',
|
|
123
|
+
backupPath: result.backupPath,
|
|
124
|
+
});
|
|
125
|
+
} catch (error) {
|
|
126
|
+
handleError(error, res, this.logger, 'Error deleting backup');
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { StatusCodes } from '#core/constants/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Controller for configuration-related HTTP endpoints
|
|
5
|
+
* Handles HTTP request/response translation for config operations
|
|
6
|
+
*/
|
|
7
|
+
export class ConfigController {
|
|
8
|
+
constructor(configService, logger) {
|
|
9
|
+
this.configService = configService;
|
|
10
|
+
this.logger = logger;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extract services from config file
|
|
15
|
+
*/
|
|
16
|
+
extractServices = (req, res) => {
|
|
17
|
+
try {
|
|
18
|
+
const { filePath, fileContent } = req.body;
|
|
19
|
+
|
|
20
|
+
if (!filePath && !fileContent) {
|
|
21
|
+
return res.status(StatusCodes.BAD_REQUEST).json({
|
|
22
|
+
error: 'Either filePath or fileContent is required',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const result = this.configService.extractServicesFromFile(filePath, fileContent);
|
|
27
|
+
|
|
28
|
+
if (!result.success) {
|
|
29
|
+
const statusCode =
|
|
30
|
+
result.error === 'File not found' ? StatusCodes.NOT_FOUND : StatusCodes.BAD_REQUEST;
|
|
31
|
+
return res.status(statusCode).json(result);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
res.json(result);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
this.logger?.error({ error: error.message }, 'Error extracting services');
|
|
37
|
+
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
|
|
38
|
+
error: 'Failed to extract services',
|
|
39
|
+
details: error.message,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Read config file
|
|
46
|
+
*/
|
|
47
|
+
readConfig = (req, res) => {
|
|
48
|
+
try {
|
|
49
|
+
const { filePath } = req.query;
|
|
50
|
+
|
|
51
|
+
if (!filePath) {
|
|
52
|
+
return res.status(StatusCodes.BAD_REQUEST).json({ error: 'filePath is required' });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const result = this.configService.readConfigFileWithMetadata(filePath);
|
|
56
|
+
|
|
57
|
+
if (!result.success) {
|
|
58
|
+
return res.status(StatusCodes.NOT_FOUND).json(result);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
res.json(result);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
this.logger?.error({ error: error.message }, 'Error reading config');
|
|
64
|
+
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
|
|
65
|
+
error: 'Failed to read file',
|
|
66
|
+
details: error.message,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Detect config files on the system
|
|
73
|
+
*/
|
|
74
|
+
detectConfig = (_req, res) => {
|
|
75
|
+
try {
|
|
76
|
+
const detected = this.configService.detectConfigFiles();
|
|
77
|
+
const homeDir = this.configService.getHomeDir();
|
|
78
|
+
|
|
79
|
+
res.json({
|
|
80
|
+
detected,
|
|
81
|
+
platform: process.platform,
|
|
82
|
+
homeDir,
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
this.logger?.error({ error: error.message }, 'Error detecting config');
|
|
86
|
+
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
|
|
87
|
+
error: 'Failed to detect config files',
|
|
88
|
+
details: error.message,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { StatusCodes } from '#core/constants/StatusCodes.js';
|
|
2
|
+
import { ConversationFilters } from '#core/models/ConversationFilters.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Controller for conversation-related HTTP endpoints
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class ConversationController {
|
|
9
|
+
constructor(conversationService, serializationLib, logger) {
|
|
10
|
+
this.conversationService = conversationService;
|
|
11
|
+
this.serializationLib = serializationLib;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* GET /api/conversations
|
|
17
|
+
*/
|
|
18
|
+
getConversations(req, res) {
|
|
19
|
+
try {
|
|
20
|
+
const filters = new ConversationFilters({
|
|
21
|
+
sessionId: req.query.sessionId || null,
|
|
22
|
+
method: req.query.method || null,
|
|
23
|
+
status: req.query.status || null,
|
|
24
|
+
jsonrpcId: req.query.jsonrpcId || null,
|
|
25
|
+
startTime: req.query.startTime || null,
|
|
26
|
+
endTime: req.query.endTime || null,
|
|
27
|
+
limit: req.query.limit,
|
|
28
|
+
offset: req.query.offset,
|
|
29
|
+
});
|
|
30
|
+
const conversations = this.conversationService.getConversations(filters);
|
|
31
|
+
const serialized = this.serializationLib.serializeBigInt(conversations);
|
|
32
|
+
res.json(serialized);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
this.logger.error({ error: error.message }, 'Error in getConversations');
|
|
35
|
+
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
|
|
36
|
+
error: 'Failed to query conversations',
|
|
37
|
+
details: error.message,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Defaults } from '#core/constants/index.js';
|
|
2
|
+
import { handleError } from '../utils/errorHandler.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Controller for log-related HTTP endpoints
|
|
6
|
+
*/
|
|
7
|
+
export class LogController {
|
|
8
|
+
constructor(logService, logger) {
|
|
9
|
+
this.logService = logService;
|
|
10
|
+
this.logger = logger;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getLogs = (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const limit = Number.parseInt(req.query.limit) || Defaults.DEFAULT_LIMIT;
|
|
16
|
+
const offset = Number.parseInt(req.query.offset) || 0;
|
|
17
|
+
const logs = this.logService.getLogs({ limit, offset });
|
|
18
|
+
res.json(logs);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
handleError(error, res, this.logger, 'Error getting logs');
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
clearLogs = (_req, res) => {
|
|
25
|
+
try {
|
|
26
|
+
const result = this.logService.clearLogs();
|
|
27
|
+
res.json(result);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
handleError(error, res, this.logger, 'Error clearing logs');
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
exportLogs = (_req, res) => {
|
|
34
|
+
try {
|
|
35
|
+
const logsText = this.logService.exportLogs();
|
|
36
|
+
const filename = `mcp-shark-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.txt`;
|
|
37
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
38
|
+
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
|
39
|
+
res.send(logsText);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
handleError(error, res, this.logger, 'Error exporting logs');
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { handleError, handleValidationError } from '../utils/errorHandler.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Controller for MCP client (Playground) HTTP endpoints
|
|
5
|
+
*/
|
|
6
|
+
export class McpClientController {
|
|
7
|
+
constructor(mcpClientService, logger) {
|
|
8
|
+
this.mcpClientService = mcpClientService;
|
|
9
|
+
this.logger = logger;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
proxyRequest = async (req, res) => {
|
|
13
|
+
try {
|
|
14
|
+
const { method, params, serverName } = req.body;
|
|
15
|
+
|
|
16
|
+
if (!method) {
|
|
17
|
+
return handleValidationError('method field is required', res, this.logger);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!serverName) {
|
|
21
|
+
return handleValidationError('serverName field is required', res, this.logger);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const sessionId =
|
|
25
|
+
req.headers['mcp-session-id'] ||
|
|
26
|
+
req.headers['x-mcp-session-id'] ||
|
|
27
|
+
`playground-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
28
|
+
|
|
29
|
+
const { client } = await this.mcpClientService.getOrCreateClient(serverName, sessionId);
|
|
30
|
+
this.mcpClientService.updateLastAccessed(serverName, sessionId);
|
|
31
|
+
|
|
32
|
+
const result = await this.mcpClientService.executeMethod(client, method, params);
|
|
33
|
+
|
|
34
|
+
res.setHeader('Mcp-Session-Id', sessionId);
|
|
35
|
+
res.json({
|
|
36
|
+
result,
|
|
37
|
+
_sessionId: sessionId,
|
|
38
|
+
});
|
|
39
|
+
} catch (error) {
|
|
40
|
+
handleError(error, res, this.logger, 'Error in playground proxy');
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
cleanup = async (req, res) => {
|
|
45
|
+
try {
|
|
46
|
+
const sessionId = req.headers['mcp-session-id'] || req.headers['x-mcp-session-id'];
|
|
47
|
+
const { serverName } = req.body || {};
|
|
48
|
+
|
|
49
|
+
if (serverName && sessionId) {
|
|
50
|
+
await this.mcpClientService.closeClient(serverName, sessionId);
|
|
51
|
+
} else if (sessionId) {
|
|
52
|
+
await this.mcpClientService.cleanupSession(sessionId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
res.json({ success: true });
|
|
56
|
+
} catch (error) {
|
|
57
|
+
handleError(error, res, this.logger, 'Error cleaning up client');
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { NotFoundError, ValidationError } from '#core/libraries/index.js';
|
|
2
|
+
import { handleError } from '../utils/errorHandler.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Controller for MCP discovery HTTP endpoints
|
|
6
|
+
*/
|
|
7
|
+
export class McpDiscoveryController {
|
|
8
|
+
constructor(mcpDiscoveryService, logger) {
|
|
9
|
+
this.mcpDiscoveryService = mcpDiscoveryService;
|
|
10
|
+
this.logger = logger;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
discoverServers = async (_req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const result = await this.mcpDiscoveryService.discoverAllServers();
|
|
16
|
+
|
|
17
|
+
if (!result.success) {
|
|
18
|
+
if (result.error === 'MCP config file not found') {
|
|
19
|
+
return handleError(
|
|
20
|
+
new NotFoundError('Config file not found', null),
|
|
21
|
+
res,
|
|
22
|
+
this.logger,
|
|
23
|
+
'Error discovering servers'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
if (result.error === 'No servers found in config') {
|
|
27
|
+
return handleError(
|
|
28
|
+
new ValidationError('The config file does not contain any MCP servers', null),
|
|
29
|
+
res,
|
|
30
|
+
this.logger,
|
|
31
|
+
'Error discovering servers'
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return res.json({
|
|
37
|
+
success: true,
|
|
38
|
+
servers: result.servers,
|
|
39
|
+
});
|
|
40
|
+
} catch (error) {
|
|
41
|
+
handleError(error, res, this.logger, 'Error discovering servers');
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { StatusCodes } from '#core/constants/StatusCodes.js';
|
|
2
|
+
import { ExportFormat } from '#core/models/ExportFormat.js';
|
|
3
|
+
import { RequestFilters } from '#core/models/RequestFilters.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Controller for request-related HTTP endpoints
|
|
7
|
+
* Handles HTTP concerns: extraction, sanitization, serialization, formatting
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export class RequestController {
|
|
11
|
+
constructor(requestService, exportService, serializationLib, logger) {
|
|
12
|
+
this.requestService = requestService;
|
|
13
|
+
this.exportService = exportService;
|
|
14
|
+
this.serializationLib = serializationLib;
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Sanitize search parameter
|
|
20
|
+
*/
|
|
21
|
+
_sanitizeSearch(value) {
|
|
22
|
+
if (value !== undefined && value !== null) {
|
|
23
|
+
const trimmed = String(value).trim();
|
|
24
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extract and sanitize filters from HTTP query
|
|
31
|
+
*/
|
|
32
|
+
_extractFilters(reqQuery) {
|
|
33
|
+
return new RequestFilters({
|
|
34
|
+
sessionId: reqQuery.sessionId ? String(reqQuery.sessionId).trim() : null,
|
|
35
|
+
direction: reqQuery.direction ? String(reqQuery.direction).trim() : null,
|
|
36
|
+
method: reqQuery.method ? String(reqQuery.method).trim() : null,
|
|
37
|
+
jsonrpcMethod: reqQuery.jsonrpcMethod ? String(reqQuery.jsonrpcMethod).trim() : null,
|
|
38
|
+
statusCode: reqQuery.statusCode ? Number.parseInt(reqQuery.statusCode) : null,
|
|
39
|
+
jsonrpcId: reqQuery.jsonrpcId ? String(reqQuery.jsonrpcId).trim() : null,
|
|
40
|
+
search: this._sanitizeSearch(reqQuery.search),
|
|
41
|
+
serverName: reqQuery.serverName ? String(reqQuery.serverName).trim() : null,
|
|
42
|
+
startTime: reqQuery.startTime || null,
|
|
43
|
+
endTime: reqQuery.endTime || null,
|
|
44
|
+
limit: reqQuery.limit,
|
|
45
|
+
offset: reqQuery.offset,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* GET /api/requests
|
|
51
|
+
*/
|
|
52
|
+
getRequests(req, res) {
|
|
53
|
+
try {
|
|
54
|
+
const filters = this._extractFilters(req.query);
|
|
55
|
+
const requests = this.requestService.getRequests(filters);
|
|
56
|
+
const serialized = this.serializationLib.serializeBigInt(requests);
|
|
57
|
+
res.json(serialized);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
this.logger.error({ error: error.message }, 'Error in getRequests');
|
|
60
|
+
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
|
|
61
|
+
error: 'Failed to query requests',
|
|
62
|
+
details: error.message,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* GET /api/requests/:frameNumber
|
|
69
|
+
*/
|
|
70
|
+
getRequest(req, res) {
|
|
71
|
+
try {
|
|
72
|
+
const request = this.requestService.getRequest(req.params.frameNumber);
|
|
73
|
+
if (!request) {
|
|
74
|
+
return res.status(StatusCodes.NOT_FOUND).json({ error: 'Request not found' });
|
|
75
|
+
}
|
|
76
|
+
const serialized = this.serializationLib.serializeBigInt(request);
|
|
77
|
+
res.json(serialized);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
this.logger.error({ error: error.message }, 'Error in getRequest');
|
|
80
|
+
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
|
|
81
|
+
error: 'Failed to get request',
|
|
82
|
+
details: error.message,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* POST /api/requests/clear
|
|
89
|
+
*/
|
|
90
|
+
clearRequests(_req, res) {
|
|
91
|
+
try {
|
|
92
|
+
const result = this.requestService.clearRequests();
|
|
93
|
+
res.json({
|
|
94
|
+
success: true,
|
|
95
|
+
message: `Cleared ${result.clearedTables.length} table(s): ${result.clearedTables.join(', ')}. All captured traffic has been cleared.`,
|
|
96
|
+
});
|
|
97
|
+
} catch (error) {
|
|
98
|
+
this.logger.error({ error: error.message }, 'Error clearing requests');
|
|
99
|
+
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
|
|
100
|
+
error: 'Failed to clear traffic',
|
|
101
|
+
details: error.message,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* GET /api/requests/export
|
|
108
|
+
*/
|
|
109
|
+
exportRequests(req, res) {
|
|
110
|
+
try {
|
|
111
|
+
const format = req.query.format || ExportFormat.JSON;
|
|
112
|
+
const filters = this._extractFilters(req.query);
|
|
113
|
+
const requests = this.requestService.getRequestsForExport(filters);
|
|
114
|
+
|
|
115
|
+
const result = this.exportService.exportRequests(requests, format, this.serializationLib);
|
|
116
|
+
|
|
117
|
+
const filename = `mcp-shark-traffic-${new Date().toISOString().replace(/[:.]/g, '-')}.${result.extension}`;
|
|
118
|
+
res.setHeader('Content-Type', result.contentType);
|
|
119
|
+
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
|
120
|
+
res.send(result.content);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
this.logger.error({ error: error.message }, 'Error exporting requests');
|
|
123
|
+
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
|
|
124
|
+
error: 'Failed to export traffic',
|
|
125
|
+
details: error.message,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { StatusCodes } from '#core/constants/index.js';
|
|
2
|
+
import { handleError, handleValidationError } from '../utils/errorHandler.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Controller for Smart Scan HTTP endpoints
|
|
6
|
+
*/
|
|
7
|
+
export class ScanController {
|
|
8
|
+
constructor(scanService, scanCacheService, logger) {
|
|
9
|
+
this.scanService = scanService;
|
|
10
|
+
this.scanCacheService = scanCacheService;
|
|
11
|
+
this.logger = logger;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
createScan = async (req, res) => {
|
|
15
|
+
try {
|
|
16
|
+
const { apiToken, scanData } = req.body;
|
|
17
|
+
|
|
18
|
+
if (!apiToken) {
|
|
19
|
+
return handleValidationError('API token is required', res, this.logger);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!scanData) {
|
|
23
|
+
return handleValidationError('Scan data is required', res, this.logger);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const result = await this.scanService.createScan(scanData, apiToken);
|
|
27
|
+
return res.status(result.status).json(result.data || { error: result.error });
|
|
28
|
+
} catch (error) {
|
|
29
|
+
handleError(error, res, this.logger, 'Smart Scan API error');
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
createBatchScans = async (req, res) => {
|
|
34
|
+
try {
|
|
35
|
+
const { apiToken, servers } = req.body;
|
|
36
|
+
|
|
37
|
+
if (!apiToken) {
|
|
38
|
+
return handleValidationError('API token is required', res, this.logger);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!servers || !Array.isArray(servers) || servers.length === 0) {
|
|
42
|
+
return handleValidationError('Servers array is required', res, this.logger);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const results = await this.scanService.createBatchScans(servers, apiToken);
|
|
46
|
+
return res.json({
|
|
47
|
+
success: true,
|
|
48
|
+
results,
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
handleError(error, res, this.logger, 'Smart Scan batch API error');
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
getScan = async (req, res) => {
|
|
56
|
+
try {
|
|
57
|
+
const { scanId } = req.params;
|
|
58
|
+
const apiToken = req.headers.authorization?.replace('Bearer ', '');
|
|
59
|
+
|
|
60
|
+
if (!apiToken) {
|
|
61
|
+
return res.status(StatusCodes.UNAUTHORIZED).json({
|
|
62
|
+
error: 'API token is required',
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!scanId) {
|
|
67
|
+
return res.status(StatusCodes.BAD_REQUEST).json({
|
|
68
|
+
error: 'Scan ID is required',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const result = await this.scanService.getScan(scanId, apiToken);
|
|
73
|
+
return res.status(result.status).json(result.data || { error: result.error });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
handleError(error, res, this.logger, 'Smart Scan API error');
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
listScans = (_req, res) => {
|
|
80
|
+
try {
|
|
81
|
+
const cachedScans = this.scanCacheService.getAllCachedScanResults();
|
|
82
|
+
return res.json({
|
|
83
|
+
scans: cachedScans,
|
|
84
|
+
cached: true,
|
|
85
|
+
count: cachedScans.length,
|
|
86
|
+
});
|
|
87
|
+
} catch (error) {
|
|
88
|
+
handleError(error, res, this.logger, 'Error loading cached scans');
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
getCachedResults = (req, res) => {
|
|
93
|
+
try {
|
|
94
|
+
const { servers } = req.body;
|
|
95
|
+
|
|
96
|
+
if (!servers || !Array.isArray(servers) || servers.length === 0) {
|
|
97
|
+
return handleValidationError('Servers array is required', res, this.logger);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const results = this.scanService.getCachedResults(servers);
|
|
101
|
+
return res.json({
|
|
102
|
+
success: true,
|
|
103
|
+
results,
|
|
104
|
+
});
|
|
105
|
+
} catch (error) {
|
|
106
|
+
handleError(error, res, this.logger, 'Error getting cached results');
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
clearCache = (_req, res) => {
|
|
111
|
+
try {
|
|
112
|
+
const deletedCount = this.scanCacheService.clearAllScanResults();
|
|
113
|
+
return res.json({
|
|
114
|
+
success: true,
|
|
115
|
+
message: `Cleared ${deletedCount} cached scan result${deletedCount !== 1 ? 's' : ''}`,
|
|
116
|
+
deletedCount,
|
|
117
|
+
});
|
|
118
|
+
} catch (error) {
|
|
119
|
+
handleError(error, res, this.logger, 'Error clearing cache');
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|