@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.
Files changed (188) hide show
  1. package/README.md +32 -96
  2. package/bin/mcp-shark.js +1 -1
  3. package/core/configs/codex.js +68 -0
  4. package/core/configs/environment.js +51 -0
  5. package/{lib/common → core}/configs/index.js +16 -1
  6. package/core/constants/Defaults.js +15 -0
  7. package/core/constants/HttpStatus.js +14 -0
  8. package/core/constants/Server.js +20 -0
  9. package/core/constants/StatusCodes.js +25 -0
  10. package/core/constants/index.js +7 -0
  11. package/core/container/DependencyContainer.js +179 -0
  12. package/core/db/init.js +33 -0
  13. package/core/index.js +10 -0
  14. package/{mcp-server/lib/common/error.js → core/libraries/ErrorLibrary.js} +4 -0
  15. package/core/libraries/LoggerLibrary.js +91 -0
  16. package/core/libraries/SerializationLibrary.js +32 -0
  17. package/core/libraries/bootstrap-logger.js +19 -0
  18. package/core/libraries/errors/ApplicationError.js +97 -0
  19. package/core/libraries/index.js +17 -0
  20. package/{mcp-server/lib → core/mcp-server}/auditor/audit.js +77 -53
  21. package/core/mcp-server/index.js +192 -0
  22. package/{mcp-server/lib → core/mcp-server}/server/external/all.js +1 -1
  23. package/core/mcp-server/server/external/config.js +75 -0
  24. package/{mcp-server/lib → core/mcp-server}/server/external/single/client.js +1 -1
  25. package/{mcp-server/lib → core/mcp-server}/server/external/single/request.js +1 -1
  26. package/{mcp-server/lib → core/mcp-server}/server/external/single/run.js +20 -11
  27. package/{mcp-server/lib → core/mcp-server}/server/external/single/transport.js +1 -1
  28. package/{mcp-server/lib → core/mcp-server}/server/internal/handlers/error.js +1 -1
  29. package/core/mcp-server/server/internal/handlers/prompts-get.js +28 -0
  30. package/core/mcp-server/server/internal/handlers/prompts-list.js +21 -0
  31. package/core/mcp-server/server/internal/handlers/resources-list.js +21 -0
  32. package/core/mcp-server/server/internal/handlers/resources-read.js +28 -0
  33. package/core/mcp-server/server/internal/handlers/tools-call.js +44 -0
  34. package/core/mcp-server/server/internal/handlers/tools-list.js +23 -0
  35. package/core/mcp-server/server/internal/run.js +53 -0
  36. package/{mcp-server/lib → core/mcp-server}/server/internal/server.js +11 -1
  37. package/core/models/ConversationFilters.js +31 -0
  38. package/core/models/ExportFormat.js +8 -0
  39. package/core/models/RequestFilters.js +43 -0
  40. package/core/models/SessionFilters.js +23 -0
  41. package/core/models/index.js +8 -0
  42. package/core/repositories/AuditRepository.js +233 -0
  43. package/core/repositories/ConversationRepository.js +182 -0
  44. package/core/repositories/PacketRepository.js +237 -0
  45. package/core/repositories/SchemaRepository.js +107 -0
  46. package/core/repositories/SessionRepository.js +59 -0
  47. package/core/repositories/StatisticsRepository.js +54 -0
  48. package/core/repositories/index.js +10 -0
  49. package/core/services/AuditService.js +144 -0
  50. package/core/services/BackupService.js +222 -0
  51. package/core/services/ConfigDetectionService.js +89 -0
  52. package/core/services/ConfigFileService.js +210 -0
  53. package/core/services/ConfigPatchingService.js +137 -0
  54. package/core/services/ConfigService.js +250 -0
  55. package/core/services/ConfigTransformService.js +178 -0
  56. package/core/services/ConversationService.js +19 -0
  57. package/core/services/ExportService.js +117 -0
  58. package/core/services/LogService.js +64 -0
  59. package/core/services/McpClientService.js +235 -0
  60. package/core/services/McpDiscoveryService.js +107 -0
  61. package/core/services/RequestService.js +56 -0
  62. package/core/services/ScanCacheService.js +242 -0
  63. package/core/services/ScanService.js +167 -0
  64. package/core/services/ServerManagementService.js +206 -0
  65. package/core/services/SessionService.js +34 -0
  66. package/core/services/SettingsService.js +163 -0
  67. package/core/services/StatisticsService.js +64 -0
  68. package/core/services/TokenService.js +94 -0
  69. package/core/services/index.js +25 -0
  70. package/core/services/parsers/ConfigParserFactory.js +113 -0
  71. package/core/services/parsers/JsonConfigParser.js +66 -0
  72. package/core/services/parsers/LegacyJsonConfigParser.js +71 -0
  73. package/core/services/parsers/TomlConfigParser.js +87 -0
  74. package/core/services/parsers/index.js +4 -0
  75. package/{ui/server → core}/utils/scan-cache/directory.js +1 -1
  76. package/core/utils/validation.js +77 -0
  77. package/package.json +14 -11
  78. package/ui/dist/assets/index-CArYxKxS.js +35 -0
  79. package/ui/dist/index.html +1 -1
  80. package/ui/server/controllers/BackupController.js +129 -0
  81. package/ui/server/controllers/ConfigController.js +92 -0
  82. package/ui/server/controllers/ConversationController.js +41 -0
  83. package/ui/server/controllers/LogController.js +44 -0
  84. package/ui/server/controllers/McpClientController.js +60 -0
  85. package/ui/server/controllers/McpDiscoveryController.js +44 -0
  86. package/ui/server/controllers/RequestController.js +129 -0
  87. package/ui/server/controllers/ScanController.js +122 -0
  88. package/ui/server/controllers/ServerManagementController.js +134 -0
  89. package/ui/server/controllers/SessionController.js +57 -0
  90. package/ui/server/controllers/SettingsController.js +24 -0
  91. package/ui/server/controllers/StatisticsController.js +54 -0
  92. package/ui/server/controllers/TokenController.js +58 -0
  93. package/ui/server/controllers/index.js +17 -0
  94. package/ui/server/routes/backups/index.js +15 -9
  95. package/ui/server/routes/composite/index.js +62 -32
  96. package/ui/server/routes/composite/servers.js +20 -15
  97. package/ui/server/routes/config.js +13 -172
  98. package/ui/server/routes/conversations.js +9 -19
  99. package/ui/server/routes/help.js +4 -3
  100. package/ui/server/routes/logs.js +14 -26
  101. package/ui/server/routes/playground.js +11 -174
  102. package/ui/server/routes/requests.js +12 -232
  103. package/ui/server/routes/sessions.js +10 -21
  104. package/ui/server/routes/settings.js +10 -192
  105. package/ui/server/routes/smartscan.js +26 -15
  106. package/ui/server/routes/statistics.js +8 -79
  107. package/ui/server/setup.js +162 -0
  108. package/ui/server/swagger/paths/backups.js +151 -0
  109. package/ui/server/swagger/paths/components.js +76 -0
  110. package/ui/server/swagger/paths/config.js +117 -0
  111. package/ui/server/swagger/paths/conversations.js +29 -0
  112. package/ui/server/swagger/paths/help.js +82 -0
  113. package/ui/server/swagger/paths/logs.js +87 -0
  114. package/ui/server/swagger/paths/playground.js +49 -0
  115. package/ui/server/swagger/paths/requests.js +178 -0
  116. package/ui/server/swagger/paths/serverManagement.js +169 -0
  117. package/ui/server/swagger/paths/sessions.js +61 -0
  118. package/ui/server/swagger/paths/settings.js +31 -0
  119. package/ui/server/swagger/paths/smartScan/discovery.js +97 -0
  120. package/ui/server/swagger/paths/smartScan/index.js +13 -0
  121. package/ui/server/swagger/paths/smartScan/scans.js +151 -0
  122. package/ui/server/swagger/paths/smartScan/token.js +71 -0
  123. package/ui/server/swagger/paths/statistics.js +40 -0
  124. package/ui/server/swagger/paths.js +38 -0
  125. package/ui/server/swagger/swagger.js +37 -0
  126. package/ui/server/utils/cleanup.js +99 -0
  127. package/ui/server/utils/config.js +18 -96
  128. package/ui/server/utils/errorHandler.js +43 -0
  129. package/ui/server/utils/logger.js +2 -2
  130. package/ui/server/utils/paths.js +27 -30
  131. package/ui/server/utils/port.js +21 -21
  132. package/ui/server/utils/process.js +18 -10
  133. package/ui/server/utils/processState.js +17 -0
  134. package/ui/server/utils/signals.js +34 -0
  135. package/ui/server/websocket/broadcast.js +33 -0
  136. package/ui/server/websocket/handler.js +52 -0
  137. package/ui/server.js +51 -230
  138. package/ui/src/App.jsx +2 -0
  139. package/ui/src/CompositeSetup.jsx +23 -9
  140. package/ui/src/PacketFilters.jsx +17 -3
  141. package/ui/src/components/AlertModal.jsx +116 -0
  142. package/ui/src/components/App/ApiDocsButton.jsx +57 -0
  143. package/ui/src/components/App/useAppState.js +43 -1
  144. package/ui/src/components/BackupList.jsx +27 -3
  145. package/ui/src/utils/requestPairing.js +35 -36
  146. package/ui/src/utils/requestUtils.js +1 -0
  147. package/lib/common/db/init.js +0 -132
  148. package/lib/common/db/logger.js +0 -349
  149. package/lib/common/db/query.js +0 -403
  150. package/lib/common/logger.js +0 -90
  151. package/mcp-server/index.js +0 -138
  152. package/mcp-server/lib/server/external/config.js +0 -57
  153. package/mcp-server/lib/server/internal/handlers/prompts-get.js +0 -20
  154. package/mcp-server/lib/server/internal/handlers/prompts-list.js +0 -13
  155. package/mcp-server/lib/server/internal/handlers/resources-list.js +0 -13
  156. package/mcp-server/lib/server/internal/handlers/resources-read.js +0 -20
  157. package/mcp-server/lib/server/internal/handlers/tools-call.js +0 -35
  158. package/mcp-server/lib/server/internal/handlers/tools-list.js +0 -15
  159. package/mcp-server/lib/server/internal/run.js +0 -37
  160. package/mcp-server/mcp-shark.js +0 -22
  161. package/ui/dist/assets/index-CFHeMNwd.js +0 -35
  162. package/ui/server/routes/backups/deleteBackup.js +0 -54
  163. package/ui/server/routes/backups/listBackups.js +0 -75
  164. package/ui/server/routes/backups/restoreBackup.js +0 -83
  165. package/ui/server/routes/backups/viewBackup.js +0 -47
  166. package/ui/server/routes/composite/setup.js +0 -129
  167. package/ui/server/routes/composite/status.js +0 -7
  168. package/ui/server/routes/composite/stop.js +0 -39
  169. package/ui/server/routes/composite/utils.js +0 -45
  170. package/ui/server/routes/smartscan/discover.js +0 -118
  171. package/ui/server/routes/smartscan/scans/clearCache.js +0 -23
  172. package/ui/server/routes/smartscan/scans/createBatchScans.js +0 -124
  173. package/ui/server/routes/smartscan/scans/createScan.js +0 -43
  174. package/ui/server/routes/smartscan/scans/getCachedResults.js +0 -52
  175. package/ui/server/routes/smartscan/scans/getScan.js +0 -42
  176. package/ui/server/routes/smartscan/scans/listScans.js +0 -25
  177. package/ui/server/routes/smartscan/scans.js +0 -13
  178. package/ui/server/routes/smartscan/token.js +0 -57
  179. package/ui/server/utils/config-update.js +0 -240
  180. package/ui/server/utils/scan-cache/all-results.js +0 -197
  181. package/ui/server/utils/scan-cache/file-operations.js +0 -107
  182. package/ui/server/utils/scan-cache/hash.js +0 -47
  183. package/ui/server/utils/scan-cache/server-operations.js +0 -85
  184. package/ui/server/utils/scan-cache.js +0 -12
  185. package/ui/server/utils/smartscan-token.js +0 -43
  186. /package/{mcp-server/lib → core/mcp-server}/server/external/kv.js +0 -0
  187. /package/{mcp-server/lib → core/mcp-server}/server/internal/handlers/common.js +0 -0
  188. /package/{mcp-server/lib → core/mcp-server}/server/internal/session.js +0 -0
@@ -1,124 +0,0 @@
1
- const API_BASE_URL = 'https://smart.mcpshark.sh';
2
- import logger from '../../../utils/logger.js';
3
- import { computeMcpHash, getCachedScanResult, storeScanResult } from '../../../utils/scan-cache.js';
4
-
5
- /**
6
- * Create scan for multiple servers (one request per server)
7
- * POST /api/smartscan/scans/batch
8
- */
9
- export async function createBatchScans(req, res) {
10
- try {
11
- const { apiToken, servers } = req.body;
12
-
13
- if (!apiToken) {
14
- return res.status(400).json({
15
- error: 'API token is required',
16
- });
17
- }
18
-
19
- if (!servers || !Array.isArray(servers) || servers.length === 0) {
20
- return res.status(400).json({
21
- error: 'Servers array is required',
22
- });
23
- }
24
-
25
- const scanPromises = servers.map(async (serverData) => {
26
- const hash = computeMcpHash(serverData);
27
- const cachedResult = getCachedScanResult(hash);
28
- if (cachedResult) {
29
- logger.info({ serverName: serverData.name }, 'Using cached scan result for server');
30
- return {
31
- serverName: serverData.name,
32
- success: true,
33
- status: 200,
34
- data: cachedResult,
35
- error: null,
36
- cached: true,
37
- };
38
- }
39
-
40
- const scanData = {
41
- server: {
42
- name: serverData.name || 'unknown',
43
- description: serverData.description || null,
44
- },
45
- tools: (serverData.tools || []).map((tool) => {
46
- const toolData = {
47
- name: tool.name,
48
- description: tool.description || null,
49
- };
50
- if (tool.inputSchema && typeof tool.inputSchema === 'object') {
51
- toolData.input_schema = tool.inputSchema;
52
- }
53
- if (tool.outputSchema && typeof tool.outputSchema === 'object') {
54
- toolData.output_schema = tool.outputSchema;
55
- }
56
- return toolData;
57
- }),
58
- resources: (serverData.resources || []).map((resource) => ({
59
- uri: resource.uri,
60
- name: resource.name || null,
61
- description: resource.description || null,
62
- mimeType: resource.mimeType || null,
63
- })),
64
- prompts: (serverData.prompts || []).map((prompt) => ({
65
- name: prompt.name,
66
- description: prompt.description || null,
67
- arguments: prompt.arguments || [],
68
- })),
69
- };
70
-
71
- try {
72
- const response = await fetch(`${API_BASE_URL}/api/scans`, {
73
- method: 'POST',
74
- headers: {
75
- 'Content-Type': 'application/json',
76
- Accept: 'application/json',
77
- Authorization: `Bearer ${apiToken}`,
78
- },
79
- body: JSON.stringify(scanData),
80
- });
81
-
82
- const data = await response.json();
83
-
84
- const result = {
85
- serverName: serverData.name,
86
- success: response.ok,
87
- status: response.status,
88
- data: response.ok ? data : null,
89
- error: response.ok ? null : data.error || data.message || 'Unknown error',
90
- cached: false,
91
- };
92
-
93
- if (response.ok && data) {
94
- storeScanResult(serverData.name, hash, data);
95
- logger.info({ serverName: serverData.name }, 'Stored scan result in cache for server');
96
- }
97
-
98
- return result;
99
- } catch (error) {
100
- return {
101
- serverName: serverData.name,
102
- success: false,
103
- status: 500,
104
- data: null,
105
- error: error.message,
106
- cached: false,
107
- };
108
- }
109
- });
110
-
111
- const results = await Promise.all(scanPromises);
112
-
113
- return res.json({
114
- success: true,
115
- results,
116
- });
117
- } catch (error) {
118
- logger.error({ error: error.message }, 'Smart Scan batch API error');
119
- return res.status(500).json({
120
- error: 'Failed to create batch scans',
121
- message: error.message,
122
- });
123
- }
124
- }
@@ -1,43 +0,0 @@
1
- const API_BASE_URL = 'https://smart.mcpshark.sh';
2
- import logger from '../../../utils/logger.js';
3
-
4
- /**
5
- * Proxy POST request to create a scan
6
- * POST /api/smartscan/scans
7
- */
8
- export async function createScan(req, res) {
9
- try {
10
- const { apiToken, scanData } = req.body;
11
-
12
- if (!apiToken) {
13
- return res.status(400).json({
14
- error: 'API token is required',
15
- });
16
- }
17
-
18
- if (!scanData) {
19
- return res.status(400).json({
20
- error: 'Scan data is required',
21
- });
22
- }
23
-
24
- const response = await fetch(`${API_BASE_URL}/api/scans`, {
25
- method: 'POST',
26
- headers: {
27
- 'Content-Type': 'application/json',
28
- Accept: 'application/json',
29
- Authorization: `Bearer ${apiToken}`,
30
- },
31
- body: JSON.stringify(scanData),
32
- });
33
-
34
- const data = await response.json();
35
- return res.status(response.status).json(data);
36
- } catch (error) {
37
- logger.error({ error: error.message }, 'Smart Scan API error');
38
- return res.status(500).json({
39
- error: 'Failed to create scan',
40
- message: error.message,
41
- });
42
- }
43
- }
@@ -1,52 +0,0 @@
1
- import logger from '../../../utils/logger.js';
2
- import { computeMcpHash, getCachedScanResult } from '../../../utils/scan-cache.js';
3
-
4
- /**
5
- * Get cached scan results for discovered servers
6
- * POST /api/smartscan/cached-results
7
- */
8
- export function getCachedResults(req, res) {
9
- try {
10
- const { servers } = req.body;
11
-
12
- if (!servers || !Array.isArray(servers) || servers.length === 0) {
13
- return res.status(400).json({
14
- error: 'Servers array is required',
15
- });
16
- }
17
-
18
- const cachedResults = servers.map((serverData) => {
19
- const hash = computeMcpHash(serverData);
20
- const cachedResult = getCachedScanResult(hash);
21
-
22
- if (cachedResult) {
23
- return {
24
- serverName: serverData.name,
25
- success: true,
26
- data: cachedResult,
27
- cached: true,
28
- hash,
29
- };
30
- }
31
-
32
- return {
33
- serverName: serverData.name,
34
- success: false,
35
- data: null,
36
- cached: false,
37
- hash,
38
- };
39
- });
40
-
41
- return res.json({
42
- success: true,
43
- results: cachedResults,
44
- });
45
- } catch (error) {
46
- logger.error({ error: error.message }, 'Error getting cached results');
47
- return res.status(500).json({
48
- error: 'Failed to get cached results',
49
- message: error.message,
50
- });
51
- }
52
- }
@@ -1,42 +0,0 @@
1
- const API_BASE_URL = 'https://smart.mcpshark.sh';
2
- import logger from '../../../utils/logger.js';
3
-
4
- /**
5
- * Proxy GET request to get a scan by ID
6
- * GET /api/smartscan/scans/:scanId
7
- */
8
- export async function getScan(req, res) {
9
- try {
10
- const { scanId } = req.params;
11
- const apiToken = req.headers.authorization?.replace('Bearer ', '');
12
-
13
- if (!apiToken) {
14
- return res.status(401).json({
15
- error: 'API token is required',
16
- });
17
- }
18
-
19
- if (!scanId) {
20
- return res.status(400).json({
21
- error: 'Scan ID is required',
22
- });
23
- }
24
-
25
- const response = await fetch(`${API_BASE_URL}/api/scans/${scanId}`, {
26
- method: 'GET',
27
- headers: {
28
- Accept: 'application/json',
29
- Authorization: `Bearer ${apiToken}`,
30
- },
31
- });
32
-
33
- const data = await response.json();
34
- return res.status(response.status).json(data);
35
- } catch (error) {
36
- logger.error({ error: error.message }, 'Smart Scan API error');
37
- return res.status(500).json({
38
- error: 'Failed to get scan',
39
- message: error.message,
40
- });
41
- }
42
- }
@@ -1,25 +0,0 @@
1
- import logger from '../../../utils/logger.js';
2
- import { getAllCachedScanResults } from '../../../utils/scan-cache.js';
3
-
4
- /**
5
- * List all scans from local cache only
6
- * GET /api/smartscan/scans?cache=true
7
- */
8
- export async function listScans(_req, res) {
9
- try {
10
- logger.info('Loading cached scans from local storage');
11
- const cachedScans = getAllCachedScanResults();
12
- logger.info({ count: cachedScans.length }, 'Returning cached scans');
13
- return res.json({
14
- scans: cachedScans,
15
- cached: true,
16
- count: cachedScans.length,
17
- });
18
- } catch (error) {
19
- logger.error({ error: error.message }, 'Error loading cached scans');
20
- return res.status(500).json({
21
- error: 'Failed to load cached scans',
22
- message: error.message,
23
- });
24
- }
25
- }
@@ -1,13 +0,0 @@
1
- import * as clearCacheRoute from './scans/clearCache.js';
2
- import * as createBatchScansRoute from './scans/createBatchScans.js';
3
- import * as createScanRoute from './scans/createScan.js';
4
- import * as getCachedResultsRoute from './scans/getCachedResults.js';
5
- import * as getScanRoute from './scans/getScan.js';
6
- import * as listScansRoute from './scans/listScans.js';
7
-
8
- export const createScan = createScanRoute.createScan;
9
- export const getScan = getScanRoute.getScan;
10
- export const getCachedResults = getCachedResultsRoute.getCachedResults;
11
- export const createBatchScans = createBatchScansRoute.createBatchScans;
12
- export const listScans = listScansRoute.listScans;
13
- export const clearCache = clearCacheRoute.clearCache;
@@ -1,57 +0,0 @@
1
- import logger from '../../utils/logger.js';
2
- import { readSmartScanToken, writeSmartScanToken } from '../../utils/smartscan-token.js';
3
-
4
- /**
5
- * Get stored Smart Scan token
6
- * GET /api/smartscan/token
7
- */
8
- export function getToken(_req, res) {
9
- try {
10
- const token = readSmartScanToken();
11
- return res.json({
12
- success: true,
13
- token: token || null,
14
- });
15
- } catch (error) {
16
- logger.error({ error: error.message }, 'Error reading Smart Scan token');
17
- return res.status(500).json({
18
- error: 'Failed to read token',
19
- message: error.message,
20
- });
21
- }
22
- }
23
-
24
- /**
25
- * Save Smart Scan token
26
- * POST /api/smartscan/token
27
- */
28
- export function saveToken(req, res) {
29
- try {
30
- const { token } = req.body;
31
-
32
- if (token === undefined) {
33
- return res.status(400).json({
34
- error: 'Token is required',
35
- });
36
- }
37
-
38
- const success = writeSmartScanToken(token);
39
-
40
- if (!success) {
41
- return res.status(500).json({
42
- error: 'Failed to save token',
43
- });
44
- }
45
-
46
- return res.json({
47
- success: true,
48
- message: 'Token saved successfully',
49
- });
50
- } catch (error) {
51
- logger.error({ error: error.message }, 'Error saving Smart Scan token');
52
- return res.status(500).json({
53
- error: 'Failed to save token',
54
- message: error.message,
55
- });
56
- }
57
- }
@@ -1,240 +0,0 @@
1
- import * as fs from 'node:fs';
2
- import { homedir } from 'node:os';
3
- import * as path from 'node:path';
4
- import { storeOriginalConfig } from './config.js';
5
- import logger from './logger.js';
6
-
7
- function findLatestBackup(filePath) {
8
- const dir = path.dirname(filePath);
9
- const basename = path.basename(filePath);
10
- const backups = [];
11
-
12
- if (!fs.existsSync(dir)) {
13
- return null;
14
- }
15
-
16
- try {
17
- const files = fs.readdirSync(dir);
18
-
19
- // Find backups with new format: .mcp.json-mcpshark.<datetime>.json
20
- files
21
- .filter((file) => {
22
- // Match pattern: .<basename>-mcpshark.<datetime>.json
23
- return /^\.(.+)-mcpshark\.\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.json$/.test(file);
24
- })
25
- .forEach((file) => {
26
- const match = file.match(/^\.(.+)-mcpshark\./);
27
- if (match && match[1] === basename) {
28
- const backupPath = path.join(dir, file);
29
- const stats = fs.statSync(backupPath);
30
- backups.push({
31
- backupPath,
32
- modifiedAt: stats.mtime,
33
- });
34
- }
35
- });
36
-
37
- // Also check for old .backup format
38
- const oldBackupPath = `${filePath}.backup`;
39
- if (fs.existsSync(oldBackupPath)) {
40
- const stats = fs.statSync(oldBackupPath);
41
- backups.push({
42
- backupPath: oldBackupPath,
43
- modifiedAt: stats.mtime,
44
- });
45
- }
46
-
47
- if (backups.length === 0) {
48
- return null;
49
- }
50
-
51
- // Sort by modifiedAt (latest first) and return the latest
52
- backups.sort((a, b) => b.modifiedAt - a.modifiedAt);
53
- return backups[0].backupPath;
54
- } catch (error) {
55
- logger.error({ error: error.message }, 'Error finding latest backup');
56
- return null;
57
- }
58
- }
59
-
60
- function shouldCreateBackup(
61
- latestBackupPath,
62
- resolvedFilePath,
63
- content,
64
- mcpSharkLogs,
65
- broadcastLogUpdate
66
- ) {
67
- if (!latestBackupPath || !fs.existsSync(latestBackupPath)) {
68
- return true;
69
- }
70
-
71
- try {
72
- const latestBackupContent = fs.readFileSync(latestBackupPath, 'utf-8');
73
- const currentContent = content || fs.readFileSync(resolvedFilePath, 'utf-8');
74
-
75
- // Normalize both contents for comparison (remove whitespace differences)
76
- const normalizeContent = (str) => {
77
- try {
78
- // Try to parse as JSON and re-stringify to normalize
79
- return JSON.stringify(JSON.parse(str), null, 2);
80
- } catch {
81
- // If not valid JSON, just trim
82
- return str.trim();
83
- }
84
- };
85
-
86
- const normalizedBackup = normalizeContent(latestBackupContent);
87
- const normalizedCurrent = normalizeContent(currentContent);
88
-
89
- if (normalizedBackup === normalizedCurrent) {
90
- const timestamp = new Date().toISOString();
91
- const skipLog = {
92
- timestamp,
93
- type: 'stdout',
94
- line: `[BACKUP] Skipped backup (no changes detected): ${resolvedFilePath.replace(homedir(), '~')}`,
95
- };
96
- mcpSharkLogs.push(skipLog);
97
- if (mcpSharkLogs.length > 10000) {
98
- mcpSharkLogs.shift();
99
- }
100
- broadcastLogUpdate(skipLog);
101
- return false;
102
- }
103
- return true;
104
- } catch (error) {
105
- logger.error({ error: error.message }, 'Error comparing with latest backup');
106
- // If comparison fails, create backup to be safe
107
- return true;
108
- }
109
- }
110
-
111
- function createBackup(resolvedFilePath, content, mcpSharkLogs, broadcastLogUpdate) {
112
- // Create backup with new format: .mcp.json-mcpshark.<datetime>.json
113
- const datetimeStr = formatDateTimeForBackup();
114
- const dir = path.dirname(resolvedFilePath);
115
- const basename = path.basename(resolvedFilePath);
116
- const backupPath = path.join(dir, `.${basename}-mcpshark.${datetimeStr}.json`);
117
- fs.copyFileSync(resolvedFilePath, backupPath);
118
- storeOriginalConfig(resolvedFilePath, content, backupPath);
119
-
120
- const timestamp = new Date().toISOString();
121
- const backupLog = {
122
- timestamp,
123
- type: 'stdout',
124
- line: `[BACKUP] Created backup: ${backupPath.replace(homedir(), '~')}`,
125
- };
126
- mcpSharkLogs.push(backupLog);
127
- if (mcpSharkLogs.length > 10000) {
128
- mcpSharkLogs.shift();
129
- }
130
- broadcastLogUpdate(backupLog);
131
- return backupPath;
132
- }
133
-
134
- function computeBackupPath(resolvedFilePath, content, mcpSharkLogs, broadcastLogUpdate) {
135
- if (!resolvedFilePath || !fs.existsSync(resolvedFilePath)) {
136
- return null;
137
- }
138
-
139
- // Check if we need to create a backup by comparing with latest backup
140
- const latestBackupPath = findLatestBackup(resolvedFilePath);
141
- const needsBackup = shouldCreateBackup(
142
- latestBackupPath,
143
- resolvedFilePath,
144
- content,
145
- mcpSharkLogs,
146
- broadcastLogUpdate
147
- );
148
-
149
- if (needsBackup) {
150
- return createBackup(resolvedFilePath, content, mcpSharkLogs, broadcastLogUpdate);
151
- }
152
-
153
- // Still store the original config reference even if we didn't create a new backup
154
- // Use the latest backup path if available
155
- storeOriginalConfig(resolvedFilePath, content, latestBackupPath);
156
- return null;
157
- }
158
-
159
- export function updateConfigFile(
160
- originalConfig,
161
- _selectedServiceNames,
162
- resolvedFilePath,
163
- content,
164
- mcpSharkLogs,
165
- broadcastLogUpdate
166
- ) {
167
- const [serverObject, serverType] = getServerObject(originalConfig);
168
- const updatedConfig = { ...originalConfig };
169
-
170
- if (serverObject) {
171
- const updatedServers = {};
172
- // Transform all original servers to HTTP URLs pointing to MCP shark server
173
- // Each server gets its own endpoint to avoid tool name prefixing issues
174
- Object.entries(serverObject).forEach(([name, _cfg]) => {
175
- updatedServers[name] = {
176
- type: 'http',
177
- url: `http://localhost:9851/mcp/${encodeURIComponent(name)}`,
178
- };
179
- });
180
- updatedConfig[serverType] = updatedServers;
181
- }
182
-
183
- const createdBackupPath = computeBackupPath(
184
- resolvedFilePath,
185
- content,
186
- mcpSharkLogs,
187
- broadcastLogUpdate
188
- );
189
-
190
- if (resolvedFilePath && fs.existsSync(resolvedFilePath)) {
191
- fs.writeFileSync(resolvedFilePath, JSON.stringify(updatedConfig, null, 2));
192
- logger.info({ path: resolvedFilePath }, 'Updated config file');
193
- }
194
-
195
- return { updatedConfig, backupPath: createdBackupPath };
196
- }
197
-
198
- export function getSelectedServiceNames(originalConfig, selectedServices) {
199
- if (selectedServices && Array.isArray(selectedServices) && selectedServices.length > 0) {
200
- return new Set(selectedServices);
201
- }
202
-
203
- const selectedServiceNames = new Set();
204
- const hasMcpServers = originalConfig.mcpServers && typeof originalConfig.mcpServers === 'object';
205
- const hasServers = originalConfig.servers && typeof originalConfig.servers === 'object';
206
-
207
- if (hasMcpServers) {
208
- Object.keys(originalConfig.mcpServers).forEach((name) => selectedServiceNames.add(name));
209
- } else if (hasServers) {
210
- Object.keys(originalConfig.servers).forEach((name) => selectedServiceNames.add(name));
211
- }
212
-
213
- return selectedServiceNames;
214
- }
215
-
216
- function getServerObject(originalConfig) {
217
- const hasMcpServers = originalConfig.mcpServers && typeof originalConfig.mcpServers === 'object';
218
- const hasServers = originalConfig.servers && typeof originalConfig.servers === 'object';
219
-
220
- if (hasMcpServers) {
221
- return [originalConfig.mcpServers, 'mcpServers'];
222
- }
223
-
224
- if (hasServers) {
225
- return [originalConfig.servers, 'servers'];
226
- }
227
-
228
- return [null, null];
229
- }
230
-
231
- export function formatDateTimeForBackup() {
232
- const now = new Date();
233
- const year = now.getFullYear();
234
- const month = String(now.getMonth() + 1).padStart(2, '0');
235
- const day = String(now.getDate()).padStart(2, '0');
236
- const hours = String(now.getHours()).padStart(2, '0');
237
- const minutes = String(now.getMinutes()).padStart(2, '0');
238
- const seconds = String(now.getSeconds()).padStart(2, '0');
239
- return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
240
- }