@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
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { createTransport } from '#ui/server/routes/smartscan/transport.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Service for MCP server discovery
|
|
6
|
+
* Handles discovering MCP servers and their capabilities
|
|
7
|
+
*/
|
|
8
|
+
export class McpDiscoveryService {
|
|
9
|
+
constructor(configService, logger) {
|
|
10
|
+
this.configService = configService;
|
|
11
|
+
this.logger = logger;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Discover a single MCP server
|
|
16
|
+
*/
|
|
17
|
+
async discoverServer(serverName, serverConfig) {
|
|
18
|
+
const transport = createTransport(serverConfig, serverName);
|
|
19
|
+
const client = new Client(
|
|
20
|
+
{ name: 'mcp-shark-smart-scan', version: '1.0.0' },
|
|
21
|
+
{
|
|
22
|
+
capabilities: {
|
|
23
|
+
tools: {},
|
|
24
|
+
resources: {},
|
|
25
|
+
prompts: {},
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await client.connect(transport);
|
|
32
|
+
|
|
33
|
+
const [toolsResult, resourcesResult, promptsResult] = await Promise.allSettled([
|
|
34
|
+
client.listTools(),
|
|
35
|
+
client.listResources(),
|
|
36
|
+
client.listPrompts(),
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
const tools = toolsResult.status === 'fulfilled' ? toolsResult.value?.tools || [] : [];
|
|
40
|
+
const resources =
|
|
41
|
+
resourcesResult.status === 'fulfilled' ? resourcesResult.value?.resources || [] : [];
|
|
42
|
+
const prompts =
|
|
43
|
+
promptsResult.status === 'fulfilled' ? promptsResult.value?.prompts || [] : [];
|
|
44
|
+
|
|
45
|
+
await client.close();
|
|
46
|
+
if (transport.close) {
|
|
47
|
+
await transport.close();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
name: serverName,
|
|
52
|
+
tools,
|
|
53
|
+
resources,
|
|
54
|
+
prompts,
|
|
55
|
+
};
|
|
56
|
+
} catch (error) {
|
|
57
|
+
try {
|
|
58
|
+
await client.close();
|
|
59
|
+
if (transport.close) {
|
|
60
|
+
await transport.close();
|
|
61
|
+
}
|
|
62
|
+
} catch (_closeError) {
|
|
63
|
+
// Ignore close errors
|
|
64
|
+
}
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Discover all MCP servers from config
|
|
71
|
+
*/
|
|
72
|
+
async discoverAllServers() {
|
|
73
|
+
const config = this.configService.readMcpConfig();
|
|
74
|
+
if (!config) {
|
|
75
|
+
return { success: false, error: 'MCP config file not found' };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const convertedConfig = this.configService.convertMcpServersToServers(config);
|
|
79
|
+
const servers = convertedConfig.servers || {};
|
|
80
|
+
|
|
81
|
+
if (Object.keys(servers).length === 0) {
|
|
82
|
+
return { success: false, error: 'No servers found in config' };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const discoveryPromises = Object.entries(servers).map(async ([serverName, serverConfig]) => {
|
|
86
|
+
try {
|
|
87
|
+
return await this.discoverServer(serverName, serverConfig);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
this.logger?.error({ serverName, error: error.message }, 'Error discovering server');
|
|
90
|
+
return {
|
|
91
|
+
name: serverName,
|
|
92
|
+
tools: [],
|
|
93
|
+
resources: [],
|
|
94
|
+
prompts: [],
|
|
95
|
+
error: error.message,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const discoveredServers = await Promise.all(discoveryPromises);
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
success: true,
|
|
104
|
+
servers: discoveredServers,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Defaults } from '../constants/Defaults.js';
|
|
2
|
+
/**
|
|
3
|
+
* Service for request-related business logic
|
|
4
|
+
* Uses repositories for data access
|
|
5
|
+
* HTTP-agnostic: accepts models, returns models
|
|
6
|
+
*/
|
|
7
|
+
import { RequestFilters } from '../models/RequestFilters.js';
|
|
8
|
+
|
|
9
|
+
export class RequestService {
|
|
10
|
+
constructor(packetRepository) {
|
|
11
|
+
this.packetRepository = packetRepository;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get requests with filters
|
|
16
|
+
* @param {RequestFilters} filters - Typed filter model
|
|
17
|
+
* @returns {Array} Array of packet objects (raw from repository)
|
|
18
|
+
*/
|
|
19
|
+
getRequests(filters) {
|
|
20
|
+
const repoFilters = filters.toRepositoryFilters();
|
|
21
|
+
return this.packetRepository.queryRequests(repoFilters);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get request by frame number
|
|
26
|
+
* @param {number} frameNumber - Frame number
|
|
27
|
+
* @returns {Object|null} Packet object or null if not found
|
|
28
|
+
*/
|
|
29
|
+
getRequest(frameNumber) {
|
|
30
|
+
const parsedFrameNumber = Number.parseInt(frameNumber);
|
|
31
|
+
return this.packetRepository.getByFrameNumber(parsedFrameNumber);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Clear all requests
|
|
36
|
+
* @returns {Object} Result with clearedTables array
|
|
37
|
+
*/
|
|
38
|
+
clearRequests() {
|
|
39
|
+
return this.packetRepository.clearAll();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get requests for export (no limit)
|
|
44
|
+
* @param {RequestFilters} filters - Typed filter model
|
|
45
|
+
* @returns {Array} Array of packet objects
|
|
46
|
+
*/
|
|
47
|
+
getRequestsForExport(filters) {
|
|
48
|
+
const exportFilters = new RequestFilters({
|
|
49
|
+
...filters,
|
|
50
|
+
limit: Defaults.EXPORT_LIMIT,
|
|
51
|
+
offset: Defaults.DEFAULT_OFFSET,
|
|
52
|
+
});
|
|
53
|
+
const repoFilters = exportFilters.toRepositoryFilters();
|
|
54
|
+
return this.packetRepository.queryRequests(repoFilters);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { Defaults } from '#core/constants/Defaults.js';
|
|
5
|
+
import {
|
|
6
|
+
ensureScanResultsDirectory,
|
|
7
|
+
getScanResultFilePath,
|
|
8
|
+
getScanResultsDirectory,
|
|
9
|
+
} from '#core/utils/scan-cache/directory.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Service for Smart Scan cache operations
|
|
13
|
+
* Handles caching, retrieval, and management of scan results
|
|
14
|
+
*/
|
|
15
|
+
export class ScanCacheService {
|
|
16
|
+
constructor(logger) {
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Compute SHA-256 hash of MCP server data
|
|
22
|
+
*/
|
|
23
|
+
computeMcpHash(serverData) {
|
|
24
|
+
const normalized = {
|
|
25
|
+
name: serverData.name || '',
|
|
26
|
+
tools: (serverData.tools || [])
|
|
27
|
+
.map((tool) => ({
|
|
28
|
+
name: tool.name || '',
|
|
29
|
+
description: tool.description || '',
|
|
30
|
+
inputSchema: tool.inputSchema || tool.input_schema || null,
|
|
31
|
+
outputSchema: tool.outputSchema || tool.output_schema || null,
|
|
32
|
+
}))
|
|
33
|
+
.sort((a, b) => (a.name || '').localeCompare(b.name || '')),
|
|
34
|
+
resources: (serverData.resources || [])
|
|
35
|
+
.map((resource) => ({
|
|
36
|
+
uri: resource.uri || '',
|
|
37
|
+
name: resource.name || '',
|
|
38
|
+
description: resource.description || '',
|
|
39
|
+
mimeType: resource.mimeType || resource.mime_type || null,
|
|
40
|
+
}))
|
|
41
|
+
.sort((a, b) => (a.uri || '').localeCompare(b.uri || '')),
|
|
42
|
+
prompts: (serverData.prompts || [])
|
|
43
|
+
.map((prompt) => ({
|
|
44
|
+
name: prompt.name || '',
|
|
45
|
+
description: prompt.description || '',
|
|
46
|
+
arguments: (prompt.arguments || []).sort((a, b) => {
|
|
47
|
+
const aName = (a.name || '').toString();
|
|
48
|
+
const bName = (b.name || '').toString();
|
|
49
|
+
return aName.localeCompare(bName);
|
|
50
|
+
}),
|
|
51
|
+
}))
|
|
52
|
+
.sort((a, b) => (a.name || '').localeCompare(b.name || '')),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const jsonString = JSON.stringify(normalized);
|
|
56
|
+
return createHash('sha256').update(jsonString).digest('hex');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get cached scan result by hash
|
|
61
|
+
*/
|
|
62
|
+
getCachedScanResult(hash) {
|
|
63
|
+
try {
|
|
64
|
+
const filePath = getScanResultFilePath(hash);
|
|
65
|
+
|
|
66
|
+
if (!existsSync(filePath)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const fileContent = readFileSync(filePath, 'utf8');
|
|
71
|
+
const data = JSON.parse(fileContent);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
...data.scanData,
|
|
75
|
+
cached: true,
|
|
76
|
+
cachedAt: data.createdAt,
|
|
77
|
+
updatedAt: data.updatedAt,
|
|
78
|
+
serverName: data.serverName,
|
|
79
|
+
};
|
|
80
|
+
} catch (error) {
|
|
81
|
+
this.logger?.error({ hash, error: error.message }, 'Error getting cached scan result');
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get created timestamp from existing file or use default
|
|
88
|
+
*/
|
|
89
|
+
_getCreatedAt(filePath, defaultTime) {
|
|
90
|
+
if (!existsSync(filePath)) {
|
|
91
|
+
return defaultTime;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const existingContent = readFileSync(filePath, 'utf8');
|
|
95
|
+
const existingData = JSON.parse(existingContent);
|
|
96
|
+
return existingData.createdAt || defaultTime;
|
|
97
|
+
} catch (_e) {
|
|
98
|
+
return defaultTime;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Store scan result in cache
|
|
104
|
+
*/
|
|
105
|
+
storeScanResult(serverName, hash, scanData) {
|
|
106
|
+
try {
|
|
107
|
+
const filePath = getScanResultFilePath(hash);
|
|
108
|
+
const now = Date.now();
|
|
109
|
+
const createdAt = this._getCreatedAt(filePath, now);
|
|
110
|
+
|
|
111
|
+
const dataToStore = {
|
|
112
|
+
serverName,
|
|
113
|
+
hash,
|
|
114
|
+
scanData,
|
|
115
|
+
createdAt,
|
|
116
|
+
updatedAt: now,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
writeFileSync(filePath, JSON.stringify(dataToStore, null, 2), 'utf8');
|
|
120
|
+
return true;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
this.logger?.error({ serverName, hash, error: error.message }, 'Error storing scan result');
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get all cached scan results
|
|
129
|
+
*/
|
|
130
|
+
getAllCachedScanResults() {
|
|
131
|
+
try {
|
|
132
|
+
const scanResultsDir = getScanResultsDirectory();
|
|
133
|
+
if (!existsSync(scanResultsDir)) {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const files = readdirSync(scanResultsDir).filter((f) => f.endsWith('.json'));
|
|
138
|
+
const results = [];
|
|
139
|
+
|
|
140
|
+
for (const file of files) {
|
|
141
|
+
try {
|
|
142
|
+
const filePath = join(scanResultsDir, file);
|
|
143
|
+
const fileContent = readFileSync(filePath, 'utf8');
|
|
144
|
+
const data = JSON.parse(fileContent);
|
|
145
|
+
|
|
146
|
+
const scanData = data.scanData || data;
|
|
147
|
+
const scanId = scanData.id || scanData.scan_id || data.hash || file.replace('.json', '');
|
|
148
|
+
const serverName = data.serverName || 'Unknown Server';
|
|
149
|
+
|
|
150
|
+
results.push({
|
|
151
|
+
id: scanId,
|
|
152
|
+
scan_id: scanId,
|
|
153
|
+
server: { name: serverName },
|
|
154
|
+
server_name: serverName,
|
|
155
|
+
serverName: serverName,
|
|
156
|
+
status: 'completed',
|
|
157
|
+
risk_level: scanData.overall_risk_level || scanData.risk_level || 'unknown',
|
|
158
|
+
overall_risk_level: scanData.overall_risk_level || scanData.risk_level || 'unknown',
|
|
159
|
+
created_at: data.createdAt || data.created_at || scanData.created_at,
|
|
160
|
+
updated_at: data.updatedAt || data.updated_at || scanData.updated_at,
|
|
161
|
+
cached: true,
|
|
162
|
+
hash: data.hash || file.replace('.json', ''),
|
|
163
|
+
data: scanData,
|
|
164
|
+
result: scanData,
|
|
165
|
+
});
|
|
166
|
+
} catch (error) {
|
|
167
|
+
this.logger?.warn({ file, error: error.message }, 'Error reading scan result file');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
results.sort((a, b) => {
|
|
172
|
+
const aTime = a.updated_at || a.created_at || 0;
|
|
173
|
+
const bTime = b.updated_at || b.created_at || 0;
|
|
174
|
+
return bTime - aTime;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return results;
|
|
178
|
+
} catch (error) {
|
|
179
|
+
this.logger?.error({ error: error.message }, 'Error getting all cached scan results');
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Clear all cached scan results
|
|
186
|
+
*/
|
|
187
|
+
clearAllScanResults() {
|
|
188
|
+
try {
|
|
189
|
+
const scanResultsDir = ensureScanResultsDirectory();
|
|
190
|
+
const files = readdirSync(scanResultsDir).filter((f) => f.endsWith('.json'));
|
|
191
|
+
|
|
192
|
+
const deletedCount = files.reduce((count, file) => {
|
|
193
|
+
try {
|
|
194
|
+
const filePath = join(scanResultsDir, file);
|
|
195
|
+
unlinkSync(filePath);
|
|
196
|
+
return count + 1;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
this.logger?.warn({ file, error: error.message }, 'Error deleting scan result file');
|
|
199
|
+
return count;
|
|
200
|
+
}
|
|
201
|
+
}, 0);
|
|
202
|
+
|
|
203
|
+
return deletedCount;
|
|
204
|
+
} catch (error) {
|
|
205
|
+
this.logger?.error({ error: error.message }, 'Error clearing all scan results');
|
|
206
|
+
return 0;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Clear old scan results
|
|
212
|
+
*/
|
|
213
|
+
clearOldScanResults(maxAgeMs = Defaults.SCAN_RESULTS_MAX_AGE_MS) {
|
|
214
|
+
try {
|
|
215
|
+
const scanResultsDir = ensureScanResultsDirectory();
|
|
216
|
+
const files = readdirSync(scanResultsDir).filter((f) => f.endsWith('.json'));
|
|
217
|
+
const cutoffTime = Date.now() - maxAgeMs;
|
|
218
|
+
|
|
219
|
+
const deletedCount = files.reduce((count, file) => {
|
|
220
|
+
try {
|
|
221
|
+
const filePath = join(scanResultsDir, file);
|
|
222
|
+
const fileContent = readFileSync(filePath, 'utf8');
|
|
223
|
+
const data = JSON.parse(fileContent);
|
|
224
|
+
|
|
225
|
+
if (data.updatedAt && data.updatedAt < cutoffTime) {
|
|
226
|
+
unlinkSync(filePath);
|
|
227
|
+
return count + 1;
|
|
228
|
+
}
|
|
229
|
+
return count;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
this.logger?.warn({ file, error: error.message }, 'Error processing scan result file');
|
|
232
|
+
return count;
|
|
233
|
+
}
|
|
234
|
+
}, 0);
|
|
235
|
+
|
|
236
|
+
return deletedCount;
|
|
237
|
+
} catch (error) {
|
|
238
|
+
this.logger?.error({ error: error.message }, 'Error clearing old scan results');
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for Smart Scan API operations
|
|
3
|
+
* Handles communication with external Smart Scan API
|
|
4
|
+
*/
|
|
5
|
+
export class ScanService {
|
|
6
|
+
constructor(scanCacheService, logger) {
|
|
7
|
+
this.scanCacheService = scanCacheService;
|
|
8
|
+
this.logger = logger;
|
|
9
|
+
this.apiBaseUrl = 'https://smart.mcpshark.sh';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create scan for a single server
|
|
14
|
+
*/
|
|
15
|
+
async createScan(scanData, apiToken) {
|
|
16
|
+
const response = await fetch(`${this.apiBaseUrl}/api/scans`, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: {
|
|
19
|
+
'Content-Type': 'application/json',
|
|
20
|
+
Accept: 'application/json',
|
|
21
|
+
Authorization: `Bearer ${apiToken}`,
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify(scanData),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const data = await response.json();
|
|
27
|
+
return {
|
|
28
|
+
success: response.ok,
|
|
29
|
+
status: response.status,
|
|
30
|
+
data: response.ok ? data : null,
|
|
31
|
+
error: response.ok ? null : data.error || data.message || 'Unknown error',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create scans for multiple servers
|
|
37
|
+
*/
|
|
38
|
+
async createBatchScans(servers, apiToken) {
|
|
39
|
+
const scanPromises = servers.map(async (serverData) => {
|
|
40
|
+
const hash = this.scanCacheService.computeMcpHash(serverData);
|
|
41
|
+
const cachedResult = this.scanCacheService.getCachedScanResult(hash);
|
|
42
|
+
|
|
43
|
+
if (cachedResult) {
|
|
44
|
+
this.logger?.info({ serverName: serverData.name }, 'Using cached scan result');
|
|
45
|
+
return {
|
|
46
|
+
serverName: serverData.name,
|
|
47
|
+
success: true,
|
|
48
|
+
status: 200,
|
|
49
|
+
data: cachedResult,
|
|
50
|
+
error: null,
|
|
51
|
+
cached: true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const scanData = {
|
|
56
|
+
server: {
|
|
57
|
+
name: serverData.name || 'unknown',
|
|
58
|
+
description: serverData.description || null,
|
|
59
|
+
},
|
|
60
|
+
tools: (serverData.tools || []).map((tool) => {
|
|
61
|
+
const toolData = {
|
|
62
|
+
name: tool.name,
|
|
63
|
+
description: tool.description || null,
|
|
64
|
+
};
|
|
65
|
+
if (tool.inputSchema && typeof tool.inputSchema === 'object') {
|
|
66
|
+
toolData.input_schema = tool.inputSchema;
|
|
67
|
+
}
|
|
68
|
+
if (tool.outputSchema && typeof tool.outputSchema === 'object') {
|
|
69
|
+
toolData.output_schema = tool.outputSchema;
|
|
70
|
+
}
|
|
71
|
+
return toolData;
|
|
72
|
+
}),
|
|
73
|
+
resources: (serverData.resources || []).map((resource) => ({
|
|
74
|
+
uri: resource.uri,
|
|
75
|
+
name: resource.name || null,
|
|
76
|
+
description: resource.description || null,
|
|
77
|
+
mimeType: resource.mimeType || null,
|
|
78
|
+
})),
|
|
79
|
+
prompts: (serverData.prompts || []).map((prompt) => ({
|
|
80
|
+
name: prompt.name,
|
|
81
|
+
description: prompt.description || null,
|
|
82
|
+
arguments: prompt.arguments || [],
|
|
83
|
+
})),
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const result = await this.createScan(scanData, apiToken);
|
|
88
|
+
|
|
89
|
+
const batchResult = {
|
|
90
|
+
serverName: serverData.name,
|
|
91
|
+
success: result.success,
|
|
92
|
+
status: result.status,
|
|
93
|
+
data: result.data,
|
|
94
|
+
error: result.error,
|
|
95
|
+
cached: false,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (result.success && result.data) {
|
|
99
|
+
this.scanCacheService.storeScanResult(serverData.name, hash, result.data);
|
|
100
|
+
this.logger?.info({ serverName: serverData.name }, 'Stored scan result in cache');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return batchResult;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return {
|
|
106
|
+
serverName: serverData.name,
|
|
107
|
+
success: false,
|
|
108
|
+
status: 500,
|
|
109
|
+
data: null,
|
|
110
|
+
error: error.message,
|
|
111
|
+
cached: false,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return Promise.all(scanPromises);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get scan by ID
|
|
121
|
+
*/
|
|
122
|
+
async getScan(scanId, apiToken) {
|
|
123
|
+
const response = await fetch(`${this.apiBaseUrl}/api/scans/${scanId}`, {
|
|
124
|
+
method: 'GET',
|
|
125
|
+
headers: {
|
|
126
|
+
Accept: 'application/json',
|
|
127
|
+
Authorization: `Bearer ${apiToken}`,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const data = await response.json();
|
|
132
|
+
return {
|
|
133
|
+
success: response.ok,
|
|
134
|
+
status: response.status,
|
|
135
|
+
data: response.ok ? data : null,
|
|
136
|
+
error: response.ok ? null : data.error || data.message || 'Unknown error',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get cached results for servers
|
|
142
|
+
*/
|
|
143
|
+
getCachedResults(servers) {
|
|
144
|
+
return servers.map((serverData) => {
|
|
145
|
+
const hash = this.scanCacheService.computeMcpHash(serverData);
|
|
146
|
+
const cachedResult = this.scanCacheService.getCachedScanResult(hash);
|
|
147
|
+
|
|
148
|
+
if (cachedResult) {
|
|
149
|
+
return {
|
|
150
|
+
serverName: serverData.name,
|
|
151
|
+
success: true,
|
|
152
|
+
data: cachedResult,
|
|
153
|
+
cached: true,
|
|
154
|
+
hash,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
serverName: serverData.name,
|
|
160
|
+
success: false,
|
|
161
|
+
data: null,
|
|
162
|
+
cached: false,
|
|
163
|
+
hash,
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|