@mcp-shark/mcp-shark 1.5.4 → 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 -138
- 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,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for configuration transformations
|
|
3
|
+
* Handles converting, filtering, and updating config structures
|
|
4
|
+
*/
|
|
5
|
+
export class ConfigTransformService {
|
|
6
|
+
constructor(configParserFactory) {
|
|
7
|
+
this.parserFactory = configParserFactory;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Convert MCP servers format to servers format
|
|
12
|
+
* Normalizes config first, then converts mcpServers to servers
|
|
13
|
+
*/
|
|
14
|
+
convertMcpServersToServers(config) {
|
|
15
|
+
// Normalize config to ensure consistent format
|
|
16
|
+
const normalized = this.parserFactory.normalizeToInternalFormat(config);
|
|
17
|
+
if (!normalized) {
|
|
18
|
+
return { servers: {} };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const converted = { servers: {} };
|
|
22
|
+
|
|
23
|
+
// Handle normalized servers (legacy format)
|
|
24
|
+
if (normalized.servers) {
|
|
25
|
+
converted.servers = { ...normalized.servers };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Convert mcpServers to servers format
|
|
29
|
+
if (normalized.mcpServers) {
|
|
30
|
+
for (const [name, cfg] of Object.entries(normalized.mcpServers)) {
|
|
31
|
+
const type = cfg.type || (cfg.url ? 'http' : cfg.command ? 'stdio' : 'stdio');
|
|
32
|
+
converted.servers[name] = { type, ...cfg };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return converted;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Extract services from config
|
|
41
|
+
*/
|
|
42
|
+
extractServices(config) {
|
|
43
|
+
const { mcpServers, servers } = config;
|
|
44
|
+
const servicesMap = new Map();
|
|
45
|
+
|
|
46
|
+
if (servers) {
|
|
47
|
+
for (const [name, cfg] of Object.entries(servers)) {
|
|
48
|
+
const type = cfg.type || (cfg.url ? 'http' : cfg.command ? 'stdio' : 'stdio');
|
|
49
|
+
servicesMap.set(name, {
|
|
50
|
+
name,
|
|
51
|
+
type,
|
|
52
|
+
url: cfg.url || null,
|
|
53
|
+
command: cfg.command || null,
|
|
54
|
+
args: cfg.args || null,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (mcpServers) {
|
|
60
|
+
for (const [name, cfg] of Object.entries(mcpServers)) {
|
|
61
|
+
if (!servicesMap.has(name)) {
|
|
62
|
+
const type = cfg.type || (cfg.url ? 'http' : cfg.command ? 'stdio' : 'stdio');
|
|
63
|
+
servicesMap.set(name, {
|
|
64
|
+
name,
|
|
65
|
+
type,
|
|
66
|
+
url: cfg.url || null,
|
|
67
|
+
command: cfg.command || null,
|
|
68
|
+
args: cfg.args || null,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return Array.from(servicesMap.values());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Filter servers from config by selected service names
|
|
79
|
+
*/
|
|
80
|
+
filterServers(config, selectedServices) {
|
|
81
|
+
if (!selectedServices || !Array.isArray(selectedServices) || selectedServices.length === 0) {
|
|
82
|
+
return config;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const filtered = { servers: {} };
|
|
86
|
+
for (const serviceName of selectedServices) {
|
|
87
|
+
if (config.servers?.[serviceName]) {
|
|
88
|
+
filtered.servers[serviceName] = config.servers[serviceName];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return filtered;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Update config to use MCP Shark HTTP endpoints
|
|
97
|
+
*/
|
|
98
|
+
updateConfigForMcpShark(originalConfig) {
|
|
99
|
+
const [serverObject, serverType] = this._getServerObject(originalConfig);
|
|
100
|
+
const updatedConfig = { ...originalConfig };
|
|
101
|
+
|
|
102
|
+
if (serverObject) {
|
|
103
|
+
const updatedServers = {};
|
|
104
|
+
for (const [name, _cfg] of Object.entries(serverObject)) {
|
|
105
|
+
updatedServers[name] = {
|
|
106
|
+
type: 'http',
|
|
107
|
+
url: `http://localhost:9851/mcp/${encodeURIComponent(name)}`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
updatedConfig[serverType] = updatedServers;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return updatedConfig;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get selected service names from config
|
|
118
|
+
*/
|
|
119
|
+
getSelectedServiceNames(originalConfig, selectedServices) {
|
|
120
|
+
if (selectedServices && Array.isArray(selectedServices) && selectedServices.length > 0) {
|
|
121
|
+
return new Set(selectedServices);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const selectedServiceNames = new Set();
|
|
125
|
+
const hasMcpServers =
|
|
126
|
+
originalConfig.mcpServers && typeof originalConfig.mcpServers === 'object';
|
|
127
|
+
const hasServers = originalConfig.servers && typeof originalConfig.servers === 'object';
|
|
128
|
+
|
|
129
|
+
if (hasMcpServers) {
|
|
130
|
+
for (const name of Object.keys(originalConfig.mcpServers)) {
|
|
131
|
+
selectedServiceNames.add(name);
|
|
132
|
+
}
|
|
133
|
+
} else if (hasServers) {
|
|
134
|
+
for (const name of Object.keys(originalConfig.servers)) {
|
|
135
|
+
selectedServiceNames.add(name);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return selectedServiceNames;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check if config file is patched by mcp-shark
|
|
144
|
+
* A patched config has servers with URLs pointing to localhost:9851/mcp/
|
|
145
|
+
*/
|
|
146
|
+
isConfigPatched(config) {
|
|
147
|
+
if (!config || typeof config !== 'object') {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const servers = config.servers || config.mcpServers || {};
|
|
152
|
+
for (const [_name, cfg] of Object.entries(servers)) {
|
|
153
|
+
if (cfg && typeof cfg === 'object' && cfg.url) {
|
|
154
|
+
if (cfg.url.includes('localhost:9851/mcp/') || cfg.url.includes('127.0.0.1:9851/mcp/')) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
_getServerObject(originalConfig) {
|
|
164
|
+
const hasMcpServers =
|
|
165
|
+
originalConfig.mcpServers && typeof originalConfig.mcpServers === 'object';
|
|
166
|
+
const hasServers = originalConfig.servers && typeof originalConfig.servers === 'object';
|
|
167
|
+
|
|
168
|
+
if (hasMcpServers) {
|
|
169
|
+
return [originalConfig.mcpServers, 'mcpServers'];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (hasServers) {
|
|
173
|
+
return [originalConfig.servers, 'servers'];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return [null, null];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for conversation-related business logic
|
|
3
|
+
* HTTP-agnostic: accepts models, returns models
|
|
4
|
+
*/
|
|
5
|
+
export class ConversationService {
|
|
6
|
+
constructor(conversationRepository) {
|
|
7
|
+
this.conversationRepository = conversationRepository;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get conversations with filters
|
|
12
|
+
* @param {ConversationFilters} filters - Typed filter model
|
|
13
|
+
* @returns {Array} Array of conversation objects (raw from repository)
|
|
14
|
+
*/
|
|
15
|
+
getConversations(filters) {
|
|
16
|
+
const repoFilters = filters.toRepositoryFilters();
|
|
17
|
+
return this.conversationRepository.queryConversations(repoFilters);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { ExportFormat } from '../models/ExportFormat.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Service for exporting data in various formats
|
|
5
|
+
* Handles formatting business logic for CSV, TXT, and JSON exports
|
|
6
|
+
*/
|
|
7
|
+
export class ExportService {
|
|
8
|
+
/**
|
|
9
|
+
* Format requests as CSV
|
|
10
|
+
* @param {Array} requests - Array of request objects
|
|
11
|
+
* @returns {{content: string, contentType: string, extension: string}}
|
|
12
|
+
*/
|
|
13
|
+
formatAsCsv(requests) {
|
|
14
|
+
const headers = [
|
|
15
|
+
'Frame',
|
|
16
|
+
'Time',
|
|
17
|
+
'Source',
|
|
18
|
+
'Destination',
|
|
19
|
+
'Protocol',
|
|
20
|
+
'Length',
|
|
21
|
+
'Method',
|
|
22
|
+
'Status',
|
|
23
|
+
'JSON-RPC Method',
|
|
24
|
+
'Session ID',
|
|
25
|
+
'Server Name',
|
|
26
|
+
];
|
|
27
|
+
const rows = requests.map((req) => [
|
|
28
|
+
req.frame_number || '',
|
|
29
|
+
req.timestamp_iso || '',
|
|
30
|
+
req.request?.host || '',
|
|
31
|
+
req.request?.host || '',
|
|
32
|
+
'HTTP',
|
|
33
|
+
req.length || '',
|
|
34
|
+
req.request?.method || '',
|
|
35
|
+
req.response?.status_code || '',
|
|
36
|
+
req.jsonrpc_method || '',
|
|
37
|
+
req.session_id || '',
|
|
38
|
+
req.server_name || '',
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const content = [
|
|
42
|
+
headers.join(','),
|
|
43
|
+
...rows.map((row) => row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(',')),
|
|
44
|
+
].join('\n');
|
|
45
|
+
|
|
46
|
+
return { content, contentType: 'text/csv', extension: 'csv' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Format requests as TXT
|
|
51
|
+
* @param {Array} requests - Array of request objects
|
|
52
|
+
* @returns {{content: string, contentType: string, extension: string}}
|
|
53
|
+
*/
|
|
54
|
+
formatAsTxt(requests) {
|
|
55
|
+
const content = requests
|
|
56
|
+
.map((req, idx) => {
|
|
57
|
+
const lines = [
|
|
58
|
+
`=== Request/Response #${idx + 1} (Frame ${req.frame_number || 'N/A'}) ===`,
|
|
59
|
+
`Time: ${req.timestamp_iso || 'N/A'}`,
|
|
60
|
+
`Session ID: ${req.session_id || 'N/A'}`,
|
|
61
|
+
`Server: ${req.server_name || 'N/A'}`,
|
|
62
|
+
`Direction: ${req.direction || 'N/A'}`,
|
|
63
|
+
`Method: ${req.request?.method || 'N/A'}`,
|
|
64
|
+
`Status: ${req.response?.status_code || 'N/A'}`,
|
|
65
|
+
`JSON-RPC Method: ${req.jsonrpc_method || 'N/A'}`,
|
|
66
|
+
`JSON-RPC ID: ${req.jsonrpc_id || 'N/A'}`,
|
|
67
|
+
`Length: ${req.length || 0} bytes`,
|
|
68
|
+
'',
|
|
69
|
+
'Request:',
|
|
70
|
+
JSON.stringify(req.request || {}, null, 2),
|
|
71
|
+
'',
|
|
72
|
+
'Response:',
|
|
73
|
+
JSON.stringify(req.response || {}, null, 2),
|
|
74
|
+
'',
|
|
75
|
+
'---',
|
|
76
|
+
'',
|
|
77
|
+
];
|
|
78
|
+
return lines.join('\n');
|
|
79
|
+
})
|
|
80
|
+
.join('\n');
|
|
81
|
+
|
|
82
|
+
return { content, contentType: 'text/plain', extension: 'txt' };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Format requests as JSON
|
|
87
|
+
* @param {Array} requests - Array of request objects
|
|
88
|
+
* @param {Object} serializationLib - Serialization library for BigInt handling
|
|
89
|
+
* @returns {{content: string, contentType: string, extension: string}}
|
|
90
|
+
*/
|
|
91
|
+
formatAsJson(requests, serializationLib) {
|
|
92
|
+
const serialized = serializationLib.serializeBigInt(requests);
|
|
93
|
+
return {
|
|
94
|
+
content: JSON.stringify(serialized, null, 2),
|
|
95
|
+
contentType: 'application/json',
|
|
96
|
+
extension: 'json',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Export requests in the specified format
|
|
102
|
+
* @param {Array} requests - Array of request objects
|
|
103
|
+
* @param {string} format - Export format (csv, txt, json)
|
|
104
|
+
* @param {Object} serializationLib - Serialization library for BigInt handling (required for JSON)
|
|
105
|
+
* @returns {{content: string, contentType: string, extension: string}}
|
|
106
|
+
*/
|
|
107
|
+
exportRequests(requests, format, serializationLib) {
|
|
108
|
+
if (format === ExportFormat.CSV) {
|
|
109
|
+
return this.formatAsCsv(requests);
|
|
110
|
+
}
|
|
111
|
+
if (format === ExportFormat.TXT) {
|
|
112
|
+
return this.formatAsTxt(requests);
|
|
113
|
+
}
|
|
114
|
+
// Default to JSON
|
|
115
|
+
return this.formatAsJson(requests, serializationLib);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Defaults } from '#core/constants/Defaults.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Service for log management
|
|
5
|
+
* Handles log storage, retrieval, and export
|
|
6
|
+
*/
|
|
7
|
+
export class LogService {
|
|
8
|
+
constructor(logger) {
|
|
9
|
+
this.logger = logger;
|
|
10
|
+
this.logs = [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Initialize with log array (for compatibility with existing code)
|
|
15
|
+
*/
|
|
16
|
+
initialize(logsArray) {
|
|
17
|
+
this.logs = logsArray;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get logs with filters
|
|
22
|
+
*/
|
|
23
|
+
getLogs(filters = {}) {
|
|
24
|
+
const { limit = Defaults.DEFAULT_LIMIT, offset = 0 } = filters;
|
|
25
|
+
return [...this.logs].reverse().slice(offset, offset + limit);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Clear all logs
|
|
30
|
+
*/
|
|
31
|
+
clearLogs() {
|
|
32
|
+
this.logs.length = 0;
|
|
33
|
+
return { success: true, message: 'Logs cleared' };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Export logs in text format
|
|
38
|
+
*/
|
|
39
|
+
exportLogs() {
|
|
40
|
+
const logsText = this.logs
|
|
41
|
+
.map((log) => `[${log.timestamp}] [${log.type.toUpperCase()}] ${log.line}`)
|
|
42
|
+
.join('\n');
|
|
43
|
+
|
|
44
|
+
return logsText;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Add log entry
|
|
49
|
+
*/
|
|
50
|
+
addLog(logEntry) {
|
|
51
|
+
this.logs.push(logEntry);
|
|
52
|
+
if (this.logs.length > Defaults.MAX_LOG_LINES) {
|
|
53
|
+
this.logs.shift();
|
|
54
|
+
}
|
|
55
|
+
return logEntry;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get all logs (for internal use)
|
|
60
|
+
*/
|
|
61
|
+
getAllLogs() {
|
|
62
|
+
return this.logs;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
3
|
+
import { Server as ServerConstants } from '#core/constants/Server.js';
|
|
4
|
+
import { ValidationError } from '#core/libraries/index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Service for MCP client management (Playground)
|
|
8
|
+
* Handles client connections and method execution
|
|
9
|
+
*/
|
|
10
|
+
export class McpClientService {
|
|
11
|
+
constructor(logger) {
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
this.clientSessions = new Map();
|
|
14
|
+
this.mcpServerBaseUrl = 'http://localhost:9851/mcp';
|
|
15
|
+
this.cleanupIntervalId = null;
|
|
16
|
+
this.startCleanupJob();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get session key
|
|
21
|
+
*/
|
|
22
|
+
getSessionKey(serverName, sessionId) {
|
|
23
|
+
return `${serverName}:${sessionId}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get or create client for a session and server
|
|
28
|
+
*/
|
|
29
|
+
async getOrCreateClient(serverName, sessionId) {
|
|
30
|
+
const sessionKey = this.getSessionKey(serverName, sessionId);
|
|
31
|
+
if (this.clientSessions.has(sessionKey)) {
|
|
32
|
+
return this.clientSessions.get(sessionKey);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!serverName) {
|
|
36
|
+
throw new ValidationError('Server name is required');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const mcpServerUrl = `${this.mcpServerBaseUrl}/${encodeURIComponent(serverName)}`;
|
|
40
|
+
|
|
41
|
+
const client = new Client(
|
|
42
|
+
{ name: 'mcp-shark-playground', version: '1.0.0' },
|
|
43
|
+
{
|
|
44
|
+
capabilities: {
|
|
45
|
+
tools: {},
|
|
46
|
+
resources: {},
|
|
47
|
+
prompts: {},
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const transport = new StreamableHTTPClientTransport(new URL(mcpServerUrl));
|
|
53
|
+
await client.connect(transport);
|
|
54
|
+
|
|
55
|
+
const clientWrapper = {
|
|
56
|
+
client,
|
|
57
|
+
transport,
|
|
58
|
+
createdAt: Date.now(),
|
|
59
|
+
lastAccessed: Date.now(),
|
|
60
|
+
close: async () => {
|
|
61
|
+
await client.close();
|
|
62
|
+
transport.close?.();
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
this.clientSessions.set(sessionKey, clientWrapper);
|
|
67
|
+
return clientWrapper;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Update last accessed time for a session
|
|
72
|
+
*/
|
|
73
|
+
updateLastAccessed(serverName, sessionId) {
|
|
74
|
+
const sessionKey = this.getSessionKey(serverName, sessionId);
|
|
75
|
+
const clientWrapper = this.clientSessions.get(sessionKey);
|
|
76
|
+
if (clientWrapper) {
|
|
77
|
+
clientWrapper.lastAccessed = Date.now();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Execute MCP method
|
|
83
|
+
*/
|
|
84
|
+
async executeMethod(client, method, params) {
|
|
85
|
+
switch (method) {
|
|
86
|
+
case 'tools/list':
|
|
87
|
+
return await client.listTools();
|
|
88
|
+
case 'tools/call':
|
|
89
|
+
if (!params?.name) {
|
|
90
|
+
throw new ValidationError('Tool name is required');
|
|
91
|
+
}
|
|
92
|
+
return await client.callTool({
|
|
93
|
+
name: params.name,
|
|
94
|
+
arguments: params.arguments || {},
|
|
95
|
+
});
|
|
96
|
+
case 'prompts/list':
|
|
97
|
+
return await client.listPrompts();
|
|
98
|
+
case 'prompts/get':
|
|
99
|
+
if (!params?.name) {
|
|
100
|
+
throw new ValidationError('Prompt name is required');
|
|
101
|
+
}
|
|
102
|
+
return await client.getPrompt({
|
|
103
|
+
name: params.name,
|
|
104
|
+
arguments: params.arguments || {},
|
|
105
|
+
});
|
|
106
|
+
case 'resources/list':
|
|
107
|
+
return await client.listResources();
|
|
108
|
+
case 'resources/read':
|
|
109
|
+
if (!params?.uri) {
|
|
110
|
+
throw new ValidationError('Resource URI is required');
|
|
111
|
+
}
|
|
112
|
+
return await client.readResource({ uri: params.uri });
|
|
113
|
+
default:
|
|
114
|
+
throw new ValidationError(`Unsupported method: ${method}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Close client connection
|
|
120
|
+
*/
|
|
121
|
+
async closeClient(serverName, sessionId) {
|
|
122
|
+
const sessionKey = this.getSessionKey(serverName, sessionId);
|
|
123
|
+
if (this.clientSessions.has(sessionKey)) {
|
|
124
|
+
const clientWrapper = this.clientSessions.get(sessionKey);
|
|
125
|
+
await clientWrapper.close();
|
|
126
|
+
this.clientSessions.delete(sessionKey);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Cleanup session (close all clients for a session)
|
|
134
|
+
*/
|
|
135
|
+
async cleanupSession(sessionId) {
|
|
136
|
+
const keysToDelete = [];
|
|
137
|
+
for (const [key, clientWrapper] of this.clientSessions.entries()) {
|
|
138
|
+
if (key.endsWith(`:${sessionId}`)) {
|
|
139
|
+
await clientWrapper.close();
|
|
140
|
+
keysToDelete.push(key);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
for (const key of keysToDelete) {
|
|
144
|
+
this.clientSessions.delete(key);
|
|
145
|
+
}
|
|
146
|
+
return keysToDelete.length;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Cleanup stale client sessions based on timeout
|
|
151
|
+
*/
|
|
152
|
+
async cleanupStaleSessions() {
|
|
153
|
+
const now = Date.now();
|
|
154
|
+
const staleThreshold = now - ServerConstants.MCP_CLIENT_SESSION_TIMEOUT_MS;
|
|
155
|
+
const keysToDelete = [];
|
|
156
|
+
|
|
157
|
+
for (const [key, clientWrapper] of this.clientSessions.entries()) {
|
|
158
|
+
if (clientWrapper.lastAccessed < staleThreshold) {
|
|
159
|
+
try {
|
|
160
|
+
await clientWrapper.close();
|
|
161
|
+
keysToDelete.push(key);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
this.logger?.error(
|
|
164
|
+
{ error: error.message, sessionKey: key },
|
|
165
|
+
'Error closing stale MCP client session'
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (const key of keysToDelete) {
|
|
172
|
+
this.clientSessions.delete(key);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (keysToDelete.length > 0) {
|
|
176
|
+
this.logger?.info({ count: keysToDelete.length }, 'Cleaned up stale MCP client sessions');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return keysToDelete.length;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Start periodic cleanup job for stale sessions
|
|
184
|
+
*/
|
|
185
|
+
startCleanupJob() {
|
|
186
|
+
if (this.cleanupIntervalId) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Run cleanup every 5 minutes
|
|
191
|
+
this.cleanupIntervalId = setInterval(
|
|
192
|
+
() => {
|
|
193
|
+
this.cleanupStaleSessions().catch((error) => {
|
|
194
|
+
this.logger?.error({ error: error.message }, 'Error in MCP client session cleanup job');
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
5 * 60 * 1000
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Stop cleanup job
|
|
203
|
+
*/
|
|
204
|
+
stopCleanupJob() {
|
|
205
|
+
if (this.cleanupIntervalId) {
|
|
206
|
+
clearInterval(this.cleanupIntervalId);
|
|
207
|
+
this.cleanupIntervalId = null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Cleanup all client sessions (for shutdown)
|
|
213
|
+
*/
|
|
214
|
+
async cleanupAll() {
|
|
215
|
+
this.stopCleanupJob();
|
|
216
|
+
const keysToDelete = Array.from(this.clientSessions.keys());
|
|
217
|
+
|
|
218
|
+
for (const key of keysToDelete) {
|
|
219
|
+
try {
|
|
220
|
+
const clientWrapper = this.clientSessions.get(key);
|
|
221
|
+
if (clientWrapper) {
|
|
222
|
+
await clientWrapper.close();
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
this.logger?.error(
|
|
226
|
+
{ error: error.message, sessionKey: key },
|
|
227
|
+
'Error closing MCP client session during cleanup'
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
this.clientSessions.delete(key);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return keysToDelete.length;
|
|
234
|
+
}
|
|
235
|
+
}
|