@mcp-shark/mcp-shark 1.5.13 → 1.7.2

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 (158) hide show
  1. package/README.md +482 -56
  2. package/bin/mcp-shark.js +146 -52
  3. package/core/cli/AutoFixEngine.js +93 -0
  4. package/core/cli/ConfigScanner.js +193 -0
  5. package/core/cli/DataLoader.js +200 -0
  6. package/core/cli/DeclarativeRuleEngine.js +363 -0
  7. package/core/cli/DoctorCommand.js +218 -0
  8. package/core/cli/FixHandlers.js +222 -0
  9. package/core/cli/HtmlReportGenerator.js +203 -0
  10. package/core/cli/IdeConfigPaths.js +175 -0
  11. package/core/cli/ListCommand.js +255 -0
  12. package/core/cli/LockCommand.js +164 -0
  13. package/core/cli/LockDiffEngine.js +152 -0
  14. package/core/cli/RuleRegistryConfig.js +131 -0
  15. package/core/cli/ScanCommand.js +244 -0
  16. package/core/cli/ScanService.js +200 -0
  17. package/core/cli/SecretDetector.js +92 -0
  18. package/core/cli/SharkScoreCalculator.js +109 -0
  19. package/core/cli/ToolClassifications.js +51 -0
  20. package/core/cli/ToxicFlowAnalyzer.js +212 -0
  21. package/core/cli/UpdateCommand.js +188 -0
  22. package/core/cli/WalkthroughGenerator.js +195 -0
  23. package/core/cli/WatchCommand.js +129 -0
  24. package/core/cli/YamlRuleEngine.js +197 -0
  25. package/core/cli/data/rule-packs/aauth-visibility.json +117 -0
  26. package/core/cli/data/rule-packs/agentic-security-2026.json +180 -0
  27. package/core/cli/data/rule-packs/general-security.json +173 -0
  28. package/core/cli/data/rule-packs/owasp-mcp-2026.json +244 -0
  29. package/core/cli/data/rule-packs/toxic-flow-heuristics.json +21 -0
  30. package/core/cli/data/rule-sources.json +5 -0
  31. package/core/cli/data/secret-patterns.json +18 -0
  32. package/core/cli/data/tool-classifications.json +111 -0
  33. package/core/cli/data/toxic-flow-rules.json +47 -0
  34. package/core/cli/index.js +23 -0
  35. package/core/cli/output/Banner.js +52 -0
  36. package/core/cli/output/Formatter.js +183 -0
  37. package/core/cli/output/JsonFormatter.js +106 -0
  38. package/core/cli/output/index.js +16 -0
  39. package/core/cli/secureRegistryFetch.js +157 -0
  40. package/core/cli/symbols.js +16 -0
  41. package/core/configs/environment.js +3 -1
  42. package/core/configs/index.js +3 -64
  43. package/core/container/DependencyContainer.js +4 -1
  44. package/core/mcp-server/index.js +4 -1
  45. package/core/mcp-server/server/external/all.js +10 -3
  46. package/core/mcp-server/server/external/config.js +62 -5
  47. package/core/models/RequestFilters.js +3 -0
  48. package/core/repositories/PacketRepository.js +16 -0
  49. package/core/services/AuditService.js +2 -0
  50. package/core/services/ConfigService.js +9 -1
  51. package/core/services/ConfigTransformService.js +34 -2
  52. package/core/services/RequestService.js +58 -5
  53. package/core/services/ServerManagementService.js +59 -4
  54. package/core/services/security/StaticRulesService.js +69 -13
  55. package/core/services/security/TrafficAnalysisService.js +19 -1
  56. package/core/services/security/TrafficToxicFlowService.js +154 -0
  57. package/core/services/security/aauthGraph.js +199 -0
  58. package/core/services/security/aauthParser.js +274 -0
  59. package/core/services/security/aauthSelfTest.js +346 -0
  60. package/core/services/security/index.js +2 -1
  61. package/core/services/security/rules/index.js +25 -59
  62. package/core/services/security/rules/scans/configPermissions.js +91 -0
  63. package/core/services/security/rules/scans/duplicateToolNames.js +85 -0
  64. package/core/services/security/rules/scans/insecureTransport.js +148 -0
  65. package/core/services/security/rules/scans/missingContainment.js +123 -0
  66. package/core/services/security/rules/scans/shellEnvInjection.js +101 -0
  67. package/core/services/security/rules/scans/unsafeDefaults.js +99 -0
  68. package/core/services/security/toolsListFromTrafficParser.js +70 -0
  69. package/core/tui/App.js +144 -0
  70. package/core/tui/FindingsPanel.js +115 -0
  71. package/core/tui/FixPanel.js +132 -0
  72. package/core/tui/Header.js +51 -0
  73. package/core/tui/HelpBar.js +42 -0
  74. package/core/tui/ServersPanel.js +109 -0
  75. package/core/tui/ToxicFlowsPanel.js +100 -0
  76. package/core/tui/h.js +8 -0
  77. package/core/tui/index.js +11 -0
  78. package/core/tui/render.js +22 -0
  79. package/package.json +24 -16
  80. package/ui/dist/assets/index-D6zDrtMV.js +81 -0
  81. package/ui/dist/index.html +1 -1
  82. package/ui/server/controllers/AauthController.js +279 -0
  83. package/ui/server/controllers/RequestController.js +12 -1
  84. package/ui/server/controllers/SecurityFindingsController.js +46 -1
  85. package/ui/server/routes/aauth.js +18 -0
  86. package/ui/server/routes/requests.js +8 -1
  87. package/ui/server/routes/security.js +5 -1
  88. package/ui/server/setup.js +224 -6
  89. package/ui/server/swagger/paths/components.js +55 -0
  90. package/ui/server/swagger/paths/securityTrafficFlows.js +59 -0
  91. package/ui/server/swagger/paths.js +2 -2
  92. package/ui/server/swagger/swagger.js +5 -2
  93. package/ui/server.js +1 -1
  94. package/ui/src/App.jsx +26 -52
  95. package/ui/src/PacketFilters.jsx +31 -1
  96. package/ui/src/PacketList.jsx +2 -2
  97. package/ui/src/Security.jsx +10 -0
  98. package/ui/src/TabNavigation.jsx +8 -0
  99. package/ui/src/components/AAuthBadge.jsx +92 -0
  100. package/ui/src/components/AauthExplorer/AauthExplorerGraph.jsx +231 -0
  101. package/ui/src/components/AauthExplorer/AauthExplorerView.jsx +387 -0
  102. package/ui/src/components/AauthExplorer/NodeDetailPanel.jsx +272 -0
  103. package/ui/src/components/App/ActionMenu.jsx +4 -31
  104. package/ui/src/components/App/ApiDocsButton.jsx +0 -1
  105. package/ui/src/components/App/ShutdownButton.jsx +0 -1
  106. package/ui/src/components/App/useAppState.js +19 -26
  107. package/ui/src/components/DetailsTab/AAuthIdentitySection.jsx +119 -0
  108. package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +2 -0
  109. package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +2 -0
  110. package/ui/src/components/DetectedPathsList.jsx +1 -5
  111. package/ui/src/components/FileInput.jsx +0 -1
  112. package/ui/src/components/PacketFilters/AAuthPostureFilter.jsx +81 -0
  113. package/ui/src/components/RequestRow/RequestRowMain.jsx +7 -1
  114. package/ui/src/components/Security/AAuthPosturePanel.jsx +360 -0
  115. package/ui/src/components/Security/ScannerContent.jsx +33 -1
  116. package/ui/src/components/Security/TrafficToxicFlowsPanel.jsx +253 -0
  117. package/ui/src/components/Security/securityApi.js +15 -0
  118. package/ui/src/components/Security/useSecurity.js +60 -3
  119. package/ui/src/components/ServerControl.jsx +0 -1
  120. package/ui/src/components/TabNavigation/DesktopTabs.jsx +0 -11
  121. package/ui/src/components/TabNavigationIcons.jsx +5 -0
  122. package/ui/src/components/ViewModeTabs.jsx +0 -1
  123. package/ui/src/utils/animations.js +26 -9
  124. package/core/services/security/rules/scans/agentic01GoalHijack.js +0 -130
  125. package/core/services/security/rules/scans/agentic02ToolMisuse.js +0 -129
  126. package/core/services/security/rules/scans/agentic03IdentityAbuse.js +0 -130
  127. package/core/services/security/rules/scans/agentic04SupplyChain.js +0 -130
  128. package/core/services/security/rules/scans/agentic06MemoryPoisoning.js +0 -130
  129. package/core/services/security/rules/scans/agentic07InsecureCommunication.js +0 -135
  130. package/core/services/security/rules/scans/agentic08CascadingFailures.js +0 -135
  131. package/core/services/security/rules/scans/agentic09TrustExploitation.js +0 -135
  132. package/core/services/security/rules/scans/agentic10RogueAgent.js +0 -130
  133. package/core/services/security/rules/scans/hardcodedSecrets.js +0 -130
  134. package/core/services/security/rules/scans/mcp01TokenMismanagement.js +0 -127
  135. package/core/services/security/rules/scans/mcp02ScopeCreep.js +0 -130
  136. package/core/services/security/rules/scans/mcp03ToolPoisoning.js +0 -132
  137. package/core/services/security/rules/scans/mcp04SupplyChain.js +0 -131
  138. package/core/services/security/rules/scans/mcp06PromptInjection.js +0 -200
  139. package/core/services/security/rules/scans/mcp07InsufficientAuth.js +0 -130
  140. package/core/services/security/rules/scans/mcp08LackAudit.js +0 -129
  141. package/core/services/security/rules/scans/mcp09ShadowServers.js +0 -129
  142. package/core/services/security/rules/scans/mcp10ContextInjection.js +0 -130
  143. package/ui/dist/assets/index-CiCSDYf-.js +0 -97
  144. package/ui/server/routes/help.js +0 -44
  145. package/ui/server/swagger/paths/help.js +0 -82
  146. package/ui/src/HelpGuide/HelpGuideContent.jsx +0 -118
  147. package/ui/src/HelpGuide/HelpGuideFooter.jsx +0 -59
  148. package/ui/src/HelpGuide/HelpGuideHeader.jsx +0 -57
  149. package/ui/src/HelpGuide.jsx +0 -78
  150. package/ui/src/IntroTour.jsx +0 -154
  151. package/ui/src/components/App/HelpButton.jsx +0 -90
  152. package/ui/src/components/TourOverlay.jsx +0 -117
  153. package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +0 -120
  154. package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +0 -71
  155. package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +0 -54
  156. package/ui/src/components/TourTooltip/useTooltipPosition.js +0 -135
  157. package/ui/src/components/TourTooltip.jsx +0 -91
  158. package/ui/src/config/tourSteps.jsx +0 -140
@@ -7,14 +7,15 @@ import { WebSocketServer } from 'ws';
7
7
 
8
8
  import { DependencyContainer } from '#core';
9
9
  import { getDatabaseFile, prepareAppDataSpaces } from '#core/configs/index.js';
10
+ import { Defaults } from '#core/constants/Defaults.js';
10
11
  import { Server as ServerConstants } from '#core/constants/Server.js';
11
12
  import { openDb } from '#core/db/init.js';
12
13
 
14
+ import { createAauthRoutes } from './routes/aauth.js';
13
15
  import { createBackupRoutes } from './routes/backups/index.js';
14
16
  import { createCompositeRoutes } from './routes/composite/index.js';
15
17
  import { createConfigRoutes } from './routes/config.js';
16
18
  import { createConversationsRoutes } from './routes/conversations.js';
17
- import { createHelpRoutes } from './routes/help.js';
18
19
  import { createLogsRoutes } from './routes/logs.js';
19
20
  import { createPlaygroundRoutes } from './routes/playground.js';
20
21
  import { createRequestsRoutes } from './routes/requests.js';
@@ -77,7 +78,6 @@ export function createUIServer() {
77
78
  (logEntry) => broadcastLogUpdate(clients, logEntry),
78
79
  cleanup
79
80
  );
80
- const helpRoutes = createHelpRoutes();
81
81
  const playgroundRoutes = createPlaygroundRoutes(container);
82
82
  const smartScanRoutes = createSmartScanRoutes(container);
83
83
  const settingsRoutes = createSettingsRoutes(container);
@@ -98,6 +98,15 @@ export function createUIServer() {
98
98
 
99
99
  app.get('/api/statistics', statisticsRoutes.getStatistics);
100
100
 
101
+ // AAuth visibility (observation only — no verification, no enforcement)
102
+ const aauthRoutes = createAauthRoutes(container);
103
+ app.get('/api/aauth/posture', aauthRoutes.getPosture);
104
+ app.get('/api/aauth/missions', aauthRoutes.getMissions);
105
+ app.get('/api/aauth/graph', aauthRoutes.getGraph);
106
+ app.get('/api/aauth/upstreams', aauthRoutes.getUpstreams);
107
+ app.get('/api/aauth/node/:category/:id', aauthRoutes.getNodePackets);
108
+ app.post('/api/aauth/self-test', aauthRoutes.runSelfTest);
109
+
101
110
  app.get('/api/composite/logs', logsRoutes.getLogs);
102
111
  app.post('/api/composite/logs/clear', logsRoutes.clearLogs);
103
112
  app.get('/api/composite/logs/export', logsRoutes.exportLogs);
@@ -118,10 +127,6 @@ export function createUIServer() {
118
127
  app.post('/api/composite/shutdown', compositeRoutes.shutdown);
119
128
  app.get('/api/composite/servers', compositeRoutes.getServers);
120
129
 
121
- app.get('/api/help/state', helpRoutes.getState);
122
- app.post('/api/help/dismiss', helpRoutes.dismiss);
123
- app.post('/api/help/reset', helpRoutes.reset);
124
-
125
130
  app.post('/api/playground/proxy', playgroundRoutes.proxyRequest);
126
131
 
127
132
  app.post('/api/smartscan/scans', smartScanRoutes.createScan);
@@ -147,6 +152,8 @@ export function createUIServer() {
147
152
  app.get('/api/security/history', securityRoutes.getScanHistory);
148
153
  app.post('/api/security/findings/clear', securityRoutes.clearFindings);
149
154
  app.delete('/api/security/scan/:scanId', securityRoutes.deleteScanFindings);
155
+ app.get('/api/security/traffic-toxic-flows', securityRoutes.getTrafficToxicFlows);
156
+ app.post('/api/security/traffic-toxic-flows/replay', securityRoutes.replayTrafficToxicFlows);
150
157
 
151
158
  // Security routes - YARA engine
152
159
  app.get('/api/security/engine/status', securityRoutes.getEngineStatus);
@@ -203,5 +210,216 @@ export function createUIServer() {
203
210
 
204
211
  const intervalId = setInterval(checkTimestampAndNotify, ServerConstants.PACKET_CHECK_INTERVAL_MS);
205
212
 
213
+ // Bring the proxy up automatically so a fresh `npm start` (including on
214
+ // a brand-new machine) gives the user a working setup without any clicks.
215
+ //
216
+ // 1. If ~/.mcp-shark/mcps.json already declares upstreams, start the
217
+ // proxy with it directly. This handles restarts, `testbed:up`, and
218
+ // any hand-edited config.
219
+ // 2. Otherwise, look for a real editor MCP config (Cursor, Windsurf,
220
+ // Codex). If one exists with actual upstreams (i.e. not just our
221
+ // own self-route from a previous run), run the full Setup flow
222
+ // against it: convert -> write mcps.json -> start proxy -> patch
223
+ // the editor config so the editor routes through us.
224
+ // 3. If neither path applies, the UI stays in monitoring-only mode and
225
+ // the Setup panel is still available for manual configuration.
226
+ autoBootstrapProxy({
227
+ container,
228
+ processState,
229
+ setMcpSharkProcess,
230
+ mcpSharkLogs,
231
+ clients,
232
+ logger,
233
+ }).catch((err) => {
234
+ logger?.warn(
235
+ { error: err?.message },
236
+ 'Auto-bootstrap of MCP proxy failed; user can run Setup manually'
237
+ );
238
+ });
239
+
206
240
  return { server, cleanup, logger, wss };
207
241
  }
242
+
243
+ async function autoBootstrapProxy(deps) {
244
+ const { container, logger } = deps;
245
+ const configService = container.getService('config');
246
+ const mcpsJsonPath = configService.getMcpConfigPath();
247
+
248
+ // Phase 1: existing healthy mcps.json wins.
249
+ if (await tryStartFromExistingMcpsJson({ ...deps, mcpsJsonPath })) {
250
+ return;
251
+ }
252
+
253
+ // Phase 2: only auto-import on a *brand-new* install — i.e. no
254
+ // mcps.json on disk at all. If the user has an existing (but empty)
255
+ // mcps.json, that's an explicit "monitoring-only" choice and we
256
+ // respect it.
257
+ const mcpsJsonExists = configService.fileExists(mcpsJsonPath);
258
+ if (!mcpsJsonExists) {
259
+ if (await tryImportFromDetectedEditorConfig({ ...deps, mcpsJsonPath })) {
260
+ return;
261
+ }
262
+ }
263
+
264
+ logger?.info(
265
+ { mcpsJsonExists },
266
+ 'No upstreams to auto-start; running in monitoring-only mode (Setup panel still available)'
267
+ );
268
+ }
269
+
270
+ async function tryStartFromExistingMcpsJson({
271
+ container,
272
+ processState,
273
+ setMcpSharkProcess,
274
+ mcpSharkLogs,
275
+ clients,
276
+ logger,
277
+ mcpsJsonPath,
278
+ }) {
279
+ const configService = container.getService('config');
280
+ const serverManagementService = container.getService('serverManagement');
281
+
282
+ if (!configService.fileExists(mcpsJsonPath)) {
283
+ return false;
284
+ }
285
+
286
+ const content = configService.readConfigFile(mcpsJsonPath);
287
+ const parsed = configService.tryParseJson(content, mcpsJsonPath);
288
+ if (!parsed) {
289
+ logger?.warn(
290
+ { path: mcpsJsonPath },
291
+ 'Existing mcps.json is not valid JSON; skipping auto-start'
292
+ );
293
+ return false;
294
+ }
295
+
296
+ const upstreamCount =
297
+ Object.keys(parsed.servers || {}).length + Object.keys(parsed.mcpServers || {}).length;
298
+ if (upstreamCount === 0) {
299
+ return false;
300
+ }
301
+
302
+ logger?.info(
303
+ { path: mcpsJsonPath, upstreams: upstreamCount },
304
+ 'Auto-starting MCP proxy from existing mcps.json'
305
+ );
306
+
307
+ await serverManagementService.startServer({
308
+ configPath: mcpsJsonPath,
309
+ port: Defaults.DEFAULT_MCP_SERVER_PORT,
310
+ onLog: forwardLogEntry(mcpSharkLogs, clients),
311
+ onError: (err) => {
312
+ logger?.error({ error: err?.message, stack: err?.stack }, 'Auto-started MCP proxy crashed');
313
+ },
314
+ onReady: () => {
315
+ logger?.info('MCP proxy ready (auto-started from existing mcps.json)');
316
+ },
317
+ });
318
+
319
+ setMcpSharkProcess(processState, serverManagementService.getServerInstance());
320
+ return true;
321
+ }
322
+
323
+ async function tryImportFromDetectedEditorConfig({
324
+ container,
325
+ processState,
326
+ setMcpSharkProcess,
327
+ mcpSharkLogs,
328
+ clients,
329
+ logger,
330
+ mcpsJsonPath,
331
+ }) {
332
+ const configService = container.getService('config');
333
+ const serverManagementService = container.getService('serverManagement');
334
+
335
+ const detected = configService.detectConfigFiles();
336
+ const candidate = pickImportableEditorConfig(detected, configService, mcpsJsonPath, logger);
337
+ if (!candidate) {
338
+ return false;
339
+ }
340
+
341
+ logger?.info(
342
+ { editor: candidate.editor, path: candidate.path, upstreams: candidate.upstreamCount },
343
+ 'Auto-importing MCP servers from detected editor config'
344
+ );
345
+
346
+ const result = await serverManagementService.setup({
347
+ filePath: candidate.path,
348
+ port: Defaults.DEFAULT_MCP_SERVER_PORT,
349
+ onLog: forwardLogEntry(mcpSharkLogs, clients),
350
+ onError: (err) => {
351
+ logger?.error({ error: err?.message, stack: err?.stack }, 'Auto-imported MCP proxy crashed');
352
+ },
353
+ onReady: () => {
354
+ logger?.info(
355
+ { editor: candidate.editor },
356
+ 'MCP proxy ready (auto-imported from editor config)'
357
+ );
358
+ },
359
+ });
360
+
361
+ if (!result?.success) {
362
+ logger?.warn(
363
+ { editor: candidate.editor, error: result?.error },
364
+ 'Auto-import setup did not complete successfully'
365
+ );
366
+ return false;
367
+ }
368
+
369
+ setMcpSharkProcess(processState, serverManagementService.getServerInstance());
370
+ return true;
371
+ }
372
+
373
+ function pickImportableEditorConfig(detected, configService, mcpsJsonPath, logger) {
374
+ if (!Array.isArray(detected)) return null;
375
+
376
+ for (const entry of detected) {
377
+ if (!entry?.exists || !entry.path) continue;
378
+
379
+ // Skip our own proxy config — that's covered by phase 1.
380
+ try {
381
+ const a = configService.fileService.resolveFilePath(entry.path);
382
+ const b = configService.fileService.resolveFilePath(mcpsJsonPath);
383
+ if (a && b && a === b) continue;
384
+ } catch {
385
+ // best-effort comparison
386
+ }
387
+
388
+ const content = configService.readConfigFile(entry.path);
389
+ if (!content) continue;
390
+ const parsed = configService.tryParseJson(content, entry.path);
391
+ if (!parsed) continue;
392
+
393
+ // If the editor config is already patched (only the mcp-shark
394
+ // self-route), we can't import anything useful from it. The user
395
+ // either configured this themselves (great — phase 1 handled it via
396
+ // mcps.json) or there is nothing to import yet.
397
+ if (configService.isConfigPatched(parsed)) {
398
+ logger?.debug?.(
399
+ { editor: entry.editor, path: entry.path },
400
+ 'Skipping already-patched editor config during auto-import'
401
+ );
402
+ continue;
403
+ }
404
+
405
+ const services = configService.extractServices(parsed) || [];
406
+ if (services.length === 0) continue;
407
+
408
+ return { ...entry, upstreamCount: services.length };
409
+ }
410
+
411
+ return null;
412
+ }
413
+
414
+ function forwardLogEntry(mcpSharkLogs, clients) {
415
+ return (logEntry) => {
416
+ mcpSharkLogs.push(logEntry);
417
+ for (const ws of clients) {
418
+ try {
419
+ ws.send(JSON.stringify({ type: 'log', payload: logEntry }));
420
+ } catch {
421
+ // best-effort; UI tolerates dropped log messages
422
+ }
423
+ }
424
+ };
425
+ }
@@ -72,5 +72,60 @@ export const components = {
72
72
  results: { type: 'object' },
73
73
  },
74
74
  },
75
+ TrafficToxicFlowItem: {
76
+ type: 'object',
77
+ properties: {
78
+ risk: { type: 'string', example: 'HIGH' },
79
+ title: { type: 'string' },
80
+ source: { type: 'string' },
81
+ target: { type: 'string' },
82
+ scenario: { type: 'string' },
83
+ sourceIde: { type: 'string' },
84
+ targetIde: { type: 'string' },
85
+ },
86
+ },
87
+ TrafficToxicFlowServerMeta: {
88
+ type: 'object',
89
+ properties: {
90
+ name: { type: 'string' },
91
+ toolCount: { type: 'integer' },
92
+ updatedAt: { type: 'integer' },
93
+ },
94
+ },
95
+ TrafficToxicFlowSnapshot: {
96
+ type: 'object',
97
+ properties: {
98
+ success: { type: 'boolean', example: true },
99
+ toxicFlows: {
100
+ type: 'array',
101
+ items: { $ref: '#/components/schemas/TrafficToxicFlowItem' },
102
+ },
103
+ servers: {
104
+ type: 'array',
105
+ items: { $ref: '#/components/schemas/TrafficToxicFlowServerMeta' },
106
+ },
107
+ computedAt: { type: 'integer', nullable: true },
108
+ lastReplayPacketCount: { type: 'integer' },
109
+ note: { type: 'string' },
110
+ },
111
+ },
112
+ TrafficToxicFlowReplayResponse: {
113
+ allOf: [
114
+ { $ref: '#/components/schemas/TrafficToxicFlowSnapshot' },
115
+ {
116
+ type: 'object',
117
+ properties: {
118
+ replay: {
119
+ type: 'object',
120
+ properties: {
121
+ packetRows: { type: 'integer' },
122
+ serverCount: { type: 'integer' },
123
+ flowCount: { type: 'integer' },
124
+ },
125
+ },
126
+ },
127
+ },
128
+ ],
129
+ },
75
130
  },
76
131
  };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Local Analysis — traffic-derived toxic flow endpoints
3
+ */
4
+
5
+ export const securityTrafficFlowsPaths = {
6
+ '/api/security/traffic-toxic-flows': {
7
+ get: {
8
+ tags: ['Security'],
9
+ summary: 'Get toxic flows from proxy traffic',
10
+ description:
11
+ 'Returns the latest cross-server toxic-flow heuristics built from tools/list responses observed on the HTTP proxy. In-memory model; use replay to rebuild from the packet database.',
12
+ responses: {
13
+ 200: {
14
+ description: 'Snapshot with toxicFlows, servers, computedAt, note',
15
+ content: {
16
+ 'application/json': {
17
+ schema: { $ref: '#/components/schemas/TrafficToxicFlowSnapshot' },
18
+ },
19
+ },
20
+ },
21
+ 503: {
22
+ description: 'Traffic toxic flow service unavailable',
23
+ content: {
24
+ 'application/json': {
25
+ schema: {
26
+ type: 'object',
27
+ properties: {
28
+ success: { type: 'boolean', example: false },
29
+ error: { type: 'string' },
30
+ },
31
+ },
32
+ },
33
+ },
34
+ },
35
+ },
36
+ },
37
+ },
38
+ '/api/security/traffic-toxic-flows/replay': {
39
+ post: {
40
+ tags: ['Security'],
41
+ summary: 'Replay toxic flows from stored packets',
42
+ description:
43
+ 'Scans stored HTTP response packets for tools/list-style JSON-RPC results and rebuilds the toxic-flow registry.',
44
+ responses: {
45
+ 200: {
46
+ description: 'Snapshot plus replay stats (packetRows, serverCount, flowCount)',
47
+ content: {
48
+ 'application/json': {
49
+ schema: { $ref: '#/components/schemas/TrafficToxicFlowReplayResponse' },
50
+ },
51
+ },
52
+ },
53
+ 503: {
54
+ description: 'Traffic toxic flow service unavailable',
55
+ },
56
+ },
57
+ },
58
+ },
59
+ };
@@ -7,10 +7,10 @@ import { backupsPaths } from './paths/backups.js';
7
7
  import { components } from './paths/components.js';
8
8
  import { configPaths } from './paths/config.js';
9
9
  import { conversationsPaths } from './paths/conversations.js';
10
- import { helpPaths } from './paths/help.js';
11
10
  import { logsPaths } from './paths/logs.js';
12
11
  import { playgroundPaths } from './paths/playground.js';
13
12
  import { requestsPaths } from './paths/requests.js';
13
+ import { securityTrafficFlowsPaths } from './paths/securityTrafficFlows.js';
14
14
  import { serverManagementPaths } from './paths/serverManagement.js';
15
15
  import { sessionsPaths } from './paths/sessions.js';
16
16
  import { settingsPaths } from './paths/settings.js';
@@ -22,6 +22,7 @@ import { statisticsPaths } from './paths/statistics.js';
22
22
  */
23
23
  export const paths = {
24
24
  ...requestsPaths,
25
+ ...securityTrafficFlowsPaths,
25
26
  ...sessionsPaths,
26
27
  ...conversationsPaths,
27
28
  ...statisticsPaths,
@@ -29,7 +30,6 @@ export const paths = {
29
30
  ...configPaths,
30
31
  ...backupsPaths,
31
32
  ...serverManagementPaths,
32
- ...helpPaths,
33
33
  ...playgroundPaths,
34
34
  ...smartScanPaths,
35
35
  ...settingsPaths,
@@ -4,7 +4,7 @@ export const swaggerSpec = {
4
4
  openapi: '3.0.0',
5
5
  info: {
6
6
  title: 'MCP Shark API',
7
- version: '1.5.4',
7
+ version: '1.6.0',
8
8
  description:
9
9
  'API documentation for MCP Shark - A powerful monitoring and debugging tool for Model Context Protocol (MCP) servers. Provides deep visibility into every request and response.',
10
10
  contact: {
@@ -27,10 +27,13 @@ export const swaggerSpec = {
27
27
  { name: 'Config', description: 'Configuration file management' },
28
28
  { name: 'Backups', description: 'Configuration backup management' },
29
29
  { name: 'Server Management', description: 'MCP Shark server lifecycle management' },
30
- { name: 'Help', description: 'Help and tour management' },
31
30
  { name: 'Playground', description: 'MCP playground for testing tools and resources' },
32
31
  { name: 'Smart Scan', description: 'AI-powered security analysis for MCP servers' },
33
32
  { name: 'Settings', description: 'Application settings' },
33
+ {
34
+ name: 'Security',
35
+ description: 'Local static analysis findings and traffic-derived toxic flows (HTTP proxy)',
36
+ },
34
37
  ],
35
38
  paths,
36
39
  components,
package/ui/server.js CHANGED
@@ -29,7 +29,7 @@ export async function runUIServer() {
29
29
  `Port ${port} is already in use. Please stop the existing server or use a different port.`
30
30
  );
31
31
  bootstrapLogger.error(
32
- `\n❌ Port ${port} is already in use.\n Please stop the existing server or set MCP_SHARK_PORT environment variable to use a different port.\n`
32
+ `\n❌ Port ${port} is already in use.\n Please stop the existing server or set the UI_PORT (or MCP_SHARK_PORT) environment variable to use a different port.\n`
33
33
  );
34
34
  } else {
35
35
  logger?.error({ error: error.message, stack: error.stack }, 'Server error');
package/ui/src/App.jsx CHANGED
@@ -1,16 +1,15 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useEffect } from 'react';
2
2
  import CompositeLogs from './CompositeLogs';
3
3
  import CompositeSetup from './CompositeSetup';
4
- import IntroTour from './IntroTour';
5
4
  import Security from './Security';
6
5
  import ShutdownPage from './ShutdownPage';
7
6
  import SmartScan from './SmartScan';
8
7
  import TabNavigation from './TabNavigation';
8
+ import AauthExplorerView from './components/AauthExplorer/AauthExplorerView';
9
9
  import ActionMenu from './components/App/ActionMenu';
10
10
  import TrafficTab from './components/App/TrafficTab';
11
11
  import { useAppState } from './components/App/useAppState';
12
12
  import McpPlayground from './components/McpPlayground';
13
- import { tourSteps } from './config/tourSteps.jsx';
14
13
  import { colors } from './theme';
15
14
  import { fadeIn } from './utils/animations';
16
15
 
@@ -25,12 +24,9 @@ function App() {
25
24
  setFilters,
26
25
  stats,
27
26
  firstRequestTime,
28
- showTour,
29
- setShowTour,
30
27
  prevTabRef,
31
28
  loadRequests,
32
29
  } = useAppState();
33
- const [tourKey, setTourKey] = useState(0);
34
30
 
35
31
  useEffect(() => {
36
32
  if (prevTabRef.current !== activeTab) {
@@ -51,54 +47,10 @@ function App() {
51
47
  background: colors.bgPrimary,
52
48
  }}
53
49
  >
54
- {showTour && (
55
- <IntroTour
56
- key={tourKey}
57
- steps={tourSteps}
58
- onComplete={() => setShowTour(false)}
59
- onSkip={() => setShowTour(false)}
60
- onStepChange={(stepIndex) => {
61
- const step = tourSteps[stepIndex];
62
- if (step) {
63
- if (
64
- step.target === '[data-tour="setup-tab"]' ||
65
- step.target === '[data-tour="detected-editors"]' ||
66
- step.target === '[data-tour="select-file"]' ||
67
- step.target === '[data-tour="start-button"]'
68
- ) {
69
- if (activeTab !== 'setup') {
70
- setActiveTab('setup');
71
- }
72
- } else if (step.target === '[data-tour="traffic-tab"]') {
73
- if (activeTab !== 'traffic') {
74
- setActiveTab('traffic');
75
- }
76
- } else if (step.target === '[data-tour="smart-scan-tab"]') {
77
- if (activeTab !== 'smart-scan') {
78
- setActiveTab('smart-scan');
79
- }
80
- }
81
- }
82
- }}
83
- />
84
- )}
85
- <div style={{ position: 'relative' }} data-tour="tabs">
50
+ <div style={{ position: 'relative' }}>
86
51
  <TabNavigation activeTab={activeTab} onTabChange={setActiveTab} />
87
52
  </div>
88
- <ActionMenu
89
- onHelpClick={() => {
90
- if (showTour) {
91
- setShowTour(false);
92
- setTourKey((prev) => prev + 1);
93
- setTimeout(() => {
94
- setShowTour(true);
95
- }, 100);
96
- } else {
97
- setTourKey((prev) => prev + 1);
98
- setShowTour(true);
99
- }
100
- }}
101
- />
53
+ <ActionMenu />
102
54
 
103
55
  {activeTab === 'traffic' && (
104
56
  <TrafficTab
@@ -166,6 +118,28 @@ function App() {
166
118
  />
167
119
  </div>
168
120
  )}
121
+
122
+ {activeTab === 'aauth-explorer' && (
123
+ <div
124
+ data-tab-content
125
+ style={{ flex: 1, overflow: 'hidden', width: '100%', height: '100%' }}
126
+ >
127
+ <AauthExplorerView
128
+ onOpenPacket={(frameNumber) => {
129
+ if (frameNumber == null) {
130
+ return;
131
+ }
132
+ const target = requests?.find?.((r) => r.frame_number === frameNumber);
133
+ if (target) {
134
+ setSelected(target);
135
+ } else {
136
+ setSelected({ frame_number: frameNumber });
137
+ }
138
+ setActiveTab('traffic');
139
+ }}
140
+ />
141
+ </div>
142
+ )}
169
143
  </div>
170
144
  );
171
145
  }
@@ -3,6 +3,7 @@ import anime from 'animejs';
3
3
  import { useEffect, useRef, useState } from 'react';
4
4
  import AlertModal from './components/AlertModal';
5
5
  import ConfirmationModal from './components/ConfirmationModal';
6
+ import AAuthPostureFilter from './components/PacketFilters/AAuthPostureFilter';
6
7
  import ExportControls from './components/PacketFilters/ExportControls';
7
8
  import FilterInput from './components/PacketFilters/FilterInput';
8
9
  import { colors, fonts } from './theme';
@@ -44,6 +45,15 @@ function RequestFilters({ filters, onFilterChange, stats, onClear }) {
44
45
  if (filters.jsonrpcId) {
45
46
  queryParams.append('jsonrpcId', filters.jsonrpcId);
46
47
  }
48
+ if (filters.aauthPosture) {
49
+ queryParams.append('aauthPosture', filters.aauthPosture);
50
+ }
51
+ if (filters.aauthAgent) {
52
+ queryParams.append('aauthAgent', filters.aauthAgent);
53
+ }
54
+ if (filters.aauthMission) {
55
+ queryParams.append('aauthMission', filters.aauthMission);
56
+ }
47
57
  queryParams.append('format', format);
48
58
 
49
59
  const response = await fetch(`/api/requests/export?${queryParams}`);
@@ -67,7 +77,6 @@ function RequestFilters({ filters, onFilterChange, stats, onClear }) {
67
77
  return (
68
78
  <div
69
79
  ref={filtersRef}
70
- data-tour="filters"
71
80
  style={{
72
81
  padding: '12px 16px',
73
82
  borderBottom: `1px solid ${colors.borderLight}`,
@@ -159,6 +168,27 @@ function RequestFilters({ filters, onFilterChange, stats, onClear }) {
159
168
  style={{ width: '150px' }}
160
169
  />
161
170
 
171
+ <AAuthPostureFilter
172
+ value={filters.aauthPosture || null}
173
+ onChange={(posture) => onFilterChange({ ...filters, aauthPosture: posture })}
174
+ />
175
+
176
+ <FilterInput
177
+ type="text"
178
+ placeholder="AAuth Agent (aauth:..)"
179
+ value={filters.aauthAgent || ''}
180
+ onChange={(e) => onFilterChange({ ...filters, aauthAgent: e.target.value || null })}
181
+ style={{ width: '200px' }}
182
+ />
183
+
184
+ <FilterInput
185
+ type="text"
186
+ placeholder="AAuth Mission..."
187
+ value={filters.aauthMission || ''}
188
+ onChange={(e) => onFilterChange({ ...filters, aauthMission: e.target.value || null })}
189
+ style={{ width: '180px' }}
190
+ />
191
+
162
192
  <ExportControls stats={stats} onExport={handleExport} />
163
193
 
164
194
  <button
@@ -38,11 +38,11 @@ function RequestList({ requests, selected, onSelect, firstRequestTime }) {
38
38
  if (requests.length > prevRequestsLengthRef.current) {
39
39
  const newRows = Array.from(rows).slice(prevRequestsLengthRef.current);
40
40
  if (newRows.length > 0) {
41
- staggerIn(newRows, { delay: 30, duration: 300 });
41
+ staggerIn(newRows, { delay: 30, duration: 300, fade: false });
42
42
  }
43
43
  } else {
44
44
  // Animate all rows if the list was reset
45
- staggerIn(rows, { delay: 20, duration: 300 });
45
+ staggerIn(rows, { delay: 20, duration: 300, fade: false });
46
46
  }
47
47
  prevRequestsLengthRef.current = requests.length;
48
48
  }
@@ -30,6 +30,11 @@ function Security({ onNavigateToSmartScan, onNavigateToSetup }) {
30
30
  runningServersCount,
31
31
  // Scan complete state
32
32
  scanComplete,
33
+ trafficToxicSnapshot,
34
+ trafficToxicLoading,
35
+ trafficToxicError,
36
+ loadTrafficToxicFlows,
37
+ replayTrafficToxicFlows,
33
38
  // YARA rules
34
39
  communityRules,
35
40
  engineStatus,
@@ -103,6 +108,11 @@ function Security({ onNavigateToSmartScan, onNavigateToSetup }) {
103
108
  showHistory={showHistory}
104
109
  serversAvailable={runningServersCount > 0}
105
110
  scanComplete={scanComplete}
111
+ trafficToxicSnapshot={trafficToxicSnapshot}
112
+ trafficToxicLoading={trafficToxicLoading}
113
+ trafficToxicError={trafficToxicError}
114
+ loadTrafficToxicFlows={loadTrafficToxicFlows}
115
+ replayTrafficToxicFlows={replayTrafficToxicFlows}
106
116
  />
107
117
  )}
108
118