@mcp-shark/mcp-shark 1.5.7 → 1.5.9
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 +3 -2
- package/core/configs/environment.js +10 -7
- package/core/configs/index.js +0 -1
- package/core/constants/Defaults.js +2 -0
- package/core/libraries/TransportLibrary.js +72 -0
- package/core/libraries/index.js +1 -0
- package/core/mcp-server/index.js +22 -6
- package/core/mcp-server/server/external/single/transport.js +2 -2
- package/core/services/ConfigDetectionService.js +35 -22
- package/core/services/McpDiscoveryService.js +1 -1
- package/core/services/ServerManagementService.js +25 -4
- package/package.json +2 -2
- package/ui/dist/assets/index-D1MNKhKd.js +44 -0
- package/ui/dist/index.html +1 -1
- package/ui/server/controllers/ServerManagementController.js +3 -0
- package/ui/src/App.jsx +13 -5
- package/ui/src/ShutdownPage.jsx +91 -0
- package/ui/src/components/App/ActionMenu.jsx +179 -0
- package/ui/src/components/App/ApiDocsButton.jsx +56 -35
- package/ui/src/components/App/HelpButton.jsx +57 -35
- package/ui/src/components/App/ShutdownButton.jsx +142 -0
- package/ui/src/components/ShuttingDownModal.jsx +100 -0
- package/core/configs/codex.js +0 -68
- package/ui/dist/assets/index-CArYxKxS.js +0 -35
- package/ui/server/routes/smartscan/transport.js +0 -53
package/README.md
CHANGED
|
@@ -54,6 +54,7 @@ MCP Shark is a monitoring and aggregation solution for Model Context Protocol (M
|
|
|
54
54
|
- **Security analysis**: AI-powered scanning for security risks and vulnerabilities
|
|
55
55
|
- **IDE integration**: Automatic configuration detection for Cursor, Windsurf, Codex, and other IDEs
|
|
56
56
|
- **API documentation**: Comprehensive Swagger/OpenAPI documentation for all endpoints with interactive testing
|
|
57
|
+
- **Action menu**: Expandable menu providing quick access to API docs, help tour, and server shutdown
|
|
57
58
|
|
|
58
59
|
## Documentation
|
|
59
60
|
|
|
@@ -72,7 +73,7 @@ MCP Shark is a monitoring and aggregation solution for Model Context Protocol (M
|
|
|
72
73
|
- **[Architecture](docs/architecture.md)** - System architecture and design
|
|
73
74
|
- **[Database Architecture](docs/database-architecture.md)** - Database architecture and repository pattern
|
|
74
75
|
- **[API Reference](docs/api-reference.md)** - API endpoints and WebSocket protocol
|
|
75
|
-
- **API Documentation** - Interactive Swagger/OpenAPI documentation available at `/api-docs` when server is running (or click the
|
|
76
|
+
- **API Documentation** - Interactive Swagger/OpenAPI documentation available at `/api-docs` when server is running (or click the menu button ☰ in the bottom-right corner, then select the API docs button 📡)
|
|
76
77
|
|
|
77
78
|
### Architecture & Coding Rules
|
|
78
79
|
- **[Architecture Rules](rules/ARCHITECTURE_RULES.md)** - Architecture principles and guidelines
|
|
@@ -111,4 +112,4 @@ See the [LICENSE](LICENSE) file for full terms and conditions.
|
|
|
111
112
|
|
|
112
113
|
---
|
|
113
114
|
|
|
114
|
-
**Version**: 1.5.
|
|
115
|
+
**Version**: 1.5.9 | **Homepage**: [https://mcpshark.sh](https://mcpshark.sh)
|
|
@@ -7,14 +7,9 @@ import { Server } from '#core/constants/Server.js';
|
|
|
7
7
|
* Provides validated access to environment variables with defaults
|
|
8
8
|
*/
|
|
9
9
|
export const Environment = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* @returns {string} Codex home path
|
|
13
|
-
*/
|
|
14
|
-
getCodexHome() {
|
|
15
|
-
return process.env.CODEX_HOME || join(homedir(), '.codex');
|
|
10
|
+
getEnv() {
|
|
11
|
+
return process.env;
|
|
16
12
|
},
|
|
17
|
-
|
|
18
13
|
/**
|
|
19
14
|
* Get UI server port
|
|
20
15
|
* @returns {number} UI server port (default: 9853)
|
|
@@ -48,4 +43,12 @@ export const Environment = {
|
|
|
48
43
|
getMcpSharkHome() {
|
|
49
44
|
return process.env.MCP_SHARK_HOME || join(homedir(), '.mcp-shark');
|
|
50
45
|
},
|
|
46
|
+
|
|
47
|
+
getUserProfile() {
|
|
48
|
+
return process.env.USERPROFILE || homedir();
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
getCodexHome() {
|
|
52
|
+
return process.env.CODEX_HOME || join(homedir(), '.codex');
|
|
53
|
+
},
|
|
51
54
|
};
|
package/core/configs/index.js
CHANGED
|
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
|
|
5
|
-
export * from './codex.js';
|
|
6
5
|
export { Environment } from './environment.js';
|
|
7
6
|
|
|
8
7
|
const WORKING_DIRECTORY_NAME = '.mcp-shark';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
2
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
3
|
+
import { WebSocketClientTransport } from '@modelcontextprotocol/sdk/client/websocket.js';
|
|
4
|
+
|
|
5
|
+
import { Environment } from '#core/configs/environment.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Library for MCP transport creation utilities
|
|
9
|
+
* Pure utility - no dependencies on services or repositories
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create transport for MCP server based on config
|
|
14
|
+
* @param {Object} serverConfig - Server configuration
|
|
15
|
+
* @param {string} [serverConfig.type] - Transport type (stdio, http, websocket)
|
|
16
|
+
* @param {string} [serverConfig.url] - Server URL (for http/websocket)
|
|
17
|
+
* @param {Object} [serverConfig.headers] - HTTP headers
|
|
18
|
+
* @param {string} [serverConfig.command] - Command for stdio transport
|
|
19
|
+
* @param {Array} [serverConfig.args] - Command arguments
|
|
20
|
+
* @param {Object} [serverConfig.env] - Environment variables
|
|
21
|
+
* @param {string} [serverName] - Server name for error messages
|
|
22
|
+
* @returns {Object} Transport instance
|
|
23
|
+
* @throws {Error} If transport cannot be created
|
|
24
|
+
*/
|
|
25
|
+
export function createTransport(serverConfig, serverName = null) {
|
|
26
|
+
const type = serverConfig.type || (serverConfig.url ? 'http' : 'stdio');
|
|
27
|
+
const {
|
|
28
|
+
url,
|
|
29
|
+
headers: configHeaders = {},
|
|
30
|
+
command,
|
|
31
|
+
args = [],
|
|
32
|
+
env: configEnv = {},
|
|
33
|
+
} = serverConfig;
|
|
34
|
+
|
|
35
|
+
const env = {
|
|
36
|
+
...Environment.getEnv(),
|
|
37
|
+
...configEnv,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const requestInit = { headers: { ...configHeaders } };
|
|
41
|
+
|
|
42
|
+
const errorPrefix = serverName ? `Server ${serverName}: ` : '';
|
|
43
|
+
|
|
44
|
+
switch (type) {
|
|
45
|
+
case 'stdio':
|
|
46
|
+
if (!command) {
|
|
47
|
+
throw new Error(`${errorPrefix}command is required for stdio transport`);
|
|
48
|
+
}
|
|
49
|
+
return new StdioClientTransport({ command, args, env });
|
|
50
|
+
|
|
51
|
+
case 'http':
|
|
52
|
+
case 'sse':
|
|
53
|
+
case 'streamable-http':
|
|
54
|
+
if (!url) {
|
|
55
|
+
throw new Error(`${errorPrefix}url is required for ${type} transport`);
|
|
56
|
+
}
|
|
57
|
+
return new StreamableHTTPClientTransport(new URL(url), { requestInit });
|
|
58
|
+
|
|
59
|
+
case 'ws':
|
|
60
|
+
case 'websocket':
|
|
61
|
+
if (!url) {
|
|
62
|
+
throw new Error(`${errorPrefix}url is required for websocket transport`);
|
|
63
|
+
}
|
|
64
|
+
return new WebSocketClientTransport(new URL(url));
|
|
65
|
+
|
|
66
|
+
default:
|
|
67
|
+
if (command) {
|
|
68
|
+
return new StdioClientTransport({ command, args, env });
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`${errorPrefix}unsupported transport type: ${type}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
package/core/libraries/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export { SerializationLibrary } from './SerializationLibrary.js';
|
|
6
6
|
export { LoggerLibrary } from './LoggerLibrary.js';
|
|
7
|
+
export { createTransport } from './TransportLibrary.js';
|
|
7
8
|
export { CompositeError, isError, getErrors } from './ErrorLibrary.js';
|
|
8
9
|
export {
|
|
9
10
|
ApplicationError,
|
package/core/mcp-server/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getMcpConfigPath,
|
|
12
12
|
prepareAppDataSpaces,
|
|
13
13
|
} from '#core/configs/index.js';
|
|
14
|
+
import { Defaults } from '#core/constants/Defaults.js';
|
|
14
15
|
import { initDb } from '#core/db/init.js';
|
|
15
16
|
import { withAuditRequestResponseHandler } from './auditor/audit.js';
|
|
16
17
|
import { getInternalServer } from './server/internal/run.js';
|
|
@@ -114,25 +115,27 @@ function createServerPromise(httpServer, port, serverLogger, onError, onReady) {
|
|
|
114
115
|
* @param {number} [options.port=9851] - Port to listen on
|
|
115
116
|
* @param {Function} [options.onError] - Error callback
|
|
116
117
|
* @param {Function} [options.onReady] - Ready callback
|
|
118
|
+
* @param {Function} [options.onLog] - Log callback: (type: string, message: string) => void
|
|
117
119
|
* @param {Object} options.auditLogger - Required audit logger instance (use initAuditLogger() to create)
|
|
118
120
|
* @returns {Promise<{app: Express, server: http.Server, stop: Function}>} Server instance
|
|
119
121
|
*/
|
|
120
122
|
export async function startMcpSharkServer(options = {}) {
|
|
121
123
|
const {
|
|
122
124
|
configPath = getMcpConfigPath(),
|
|
123
|
-
port =
|
|
125
|
+
port = Defaults.DEFAULT_MCP_SERVER_PORT,
|
|
124
126
|
onError,
|
|
125
127
|
onReady,
|
|
128
|
+
onLog,
|
|
126
129
|
auditLogger: providedAuditLogger,
|
|
127
130
|
} = options;
|
|
128
131
|
|
|
129
132
|
prepareAppDataSpaces();
|
|
130
133
|
|
|
131
|
-
serverLogger
|
|
132
|
-
serverLogger
|
|
133
|
-
serverLogger
|
|
134
|
-
serverLogger
|
|
135
|
-
serverLogger
|
|
134
|
+
logServerInfo(serverLogger, onLog, 'Starting MCP server...', { port });
|
|
135
|
+
logServerInfo(serverLogger, onLog, 'Config path', { path: configPath });
|
|
136
|
+
logServerInfo(serverLogger, onLog, 'Database path', { path: getDatabaseFile() });
|
|
137
|
+
logServerInfo(serverLogger, onLog, 'Working directory', { path: process.cwd() });
|
|
138
|
+
logServerInfo(serverLogger, onLog, 'PATH', { path: Environment.getPath() });
|
|
136
139
|
|
|
137
140
|
try {
|
|
138
141
|
if (!providedAuditLogger) {
|
|
@@ -190,3 +193,16 @@ export async function startMcpSharkServer(options = {}) {
|
|
|
190
193
|
throw error;
|
|
191
194
|
}
|
|
192
195
|
}
|
|
196
|
+
|
|
197
|
+
function logServerInfo(serverLogger, onLog, message, metadata) {
|
|
198
|
+
const finalMessage = `[MCP Server] ${message}`;
|
|
199
|
+
serverLogger.info({ ...metadata, message: finalMessage }, finalMessage);
|
|
200
|
+
if (onLog) {
|
|
201
|
+
onLog(
|
|
202
|
+
'stdout',
|
|
203
|
+
`${finalMessage} ${Object.entries(metadata)
|
|
204
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
205
|
+
.join(' ')}`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -2,8 +2,8 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
|
2
2
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
3
3
|
import { WebSocketClientTransport } from '@modelcontextprotocol/sdk/client/websocket.js';
|
|
4
4
|
|
|
5
|
+
import { Environment } from '#core/configs/environment.js';
|
|
5
6
|
import { CompositeError } from '#core/libraries/ErrorLibrary.js';
|
|
6
|
-
|
|
7
7
|
export class TransportError extends CompositeError {
|
|
8
8
|
constructor(message, error) {
|
|
9
9
|
super('TransportError', message, error);
|
|
@@ -20,7 +20,7 @@ export function makeTransport({
|
|
|
20
20
|
}) {
|
|
21
21
|
// Start with enhanced PATH
|
|
22
22
|
const env = {
|
|
23
|
-
...
|
|
23
|
+
...Environment.getEnv(),
|
|
24
24
|
...configEnv,
|
|
25
25
|
};
|
|
26
26
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { Environment } from '#core/configs/environment.js';
|
|
5
|
+
|
|
6
|
+
const cursorDefaultPath = path.join(homedir(), '.cursor', 'mcp.json');
|
|
7
|
+
const windsurfDefaultPath = path.join(homedir(), '.codeium', 'windsurf', 'mcp_config.json');
|
|
8
|
+
const codexDefaultPath = path.join(homedir(), '.codex', 'config.toml');
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* Service for detecting configuration files on the system
|
|
@@ -16,16 +20,24 @@ export class ConfigDetectionService {
|
|
|
16
20
|
const platform = process.platform;
|
|
17
21
|
|
|
18
22
|
const cursorPaths = [
|
|
19
|
-
|
|
23
|
+
cursorDefaultPath,
|
|
20
24
|
...(platform === 'win32'
|
|
21
|
-
? [path.join(
|
|
25
|
+
? [path.join(Environment.getUserProfile(), '.cursor', 'mcp.json')]
|
|
22
26
|
: []),
|
|
23
27
|
];
|
|
24
28
|
|
|
25
29
|
const windsurfPaths = [
|
|
26
|
-
|
|
30
|
+
windsurfDefaultPath,
|
|
31
|
+
...(platform === 'win32'
|
|
32
|
+
? [path.join(Environment.getUserProfile(), '.codeium', 'windsurf', 'mcp_config.json')]
|
|
33
|
+
: []),
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const codexPaths = [
|
|
37
|
+
codexDefaultPath,
|
|
38
|
+
path.join(Environment.getCodexHome(), 'config.toml'),
|
|
27
39
|
...(platform === 'win32'
|
|
28
|
-
? [path.join(
|
|
40
|
+
? [path.join(Environment.getUserProfile(), '.codex', 'config.toml')]
|
|
29
41
|
: []),
|
|
30
42
|
];
|
|
31
43
|
|
|
@@ -53,34 +65,35 @@ export class ConfigDetectionService {
|
|
|
53
65
|
}
|
|
54
66
|
}
|
|
55
67
|
|
|
56
|
-
const codexPath
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
for (const codexPath of codexPaths) {
|
|
69
|
+
if (fs.existsSync(codexPath)) {
|
|
70
|
+
detected.push({
|
|
71
|
+
editor: 'Codex',
|
|
72
|
+
path: codexPath,
|
|
73
|
+
displayPath: codexPath.replace(homeDir, '~'),
|
|
74
|
+
exists: true,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
64
77
|
}
|
|
65
78
|
|
|
66
79
|
const defaultPaths = [
|
|
67
80
|
{
|
|
68
81
|
editor: 'Cursor',
|
|
69
|
-
path:
|
|
70
|
-
displayPath:
|
|
71
|
-
exists: fs.existsSync(
|
|
82
|
+
path: cursorDefaultPath.replace(homeDir, '~'),
|
|
83
|
+
displayPath: cursorDefaultPath.replace(homeDir, '~'),
|
|
84
|
+
exists: fs.existsSync(cursorDefaultPath),
|
|
72
85
|
},
|
|
73
86
|
{
|
|
74
87
|
editor: 'Windsurf',
|
|
75
|
-
path:
|
|
76
|
-
displayPath:
|
|
77
|
-
exists: fs.existsSync(
|
|
88
|
+
path: windsurfDefaultPath.replace(homeDir, '~'),
|
|
89
|
+
displayPath: windsurfDefaultPath.replace(homeDir, '~'),
|
|
90
|
+
exists: fs.existsSync(windsurfDefaultPath),
|
|
78
91
|
},
|
|
79
92
|
{
|
|
80
93
|
editor: 'Codex',
|
|
81
|
-
path:
|
|
82
|
-
displayPath:
|
|
83
|
-
exists: fs.existsSync(
|
|
94
|
+
path: codexDefaultPath.replace(homeDir, '~'),
|
|
95
|
+
displayPath: codexDefaultPath.replace(homeDir, '~'),
|
|
96
|
+
exists: fs.existsSync(codexDefaultPath),
|
|
84
97
|
},
|
|
85
98
|
];
|
|
86
99
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Defaults } from '#core/constants/Defaults.js';
|
|
1
2
|
import { initAuditLogger, startMcpSharkServer } from '#core/mcp-server/index.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -19,13 +20,21 @@ export class ServerManagementService {
|
|
|
19
20
|
* @param {string} [options.filePath] - Path to config file
|
|
20
21
|
* @param {string} [options.fileContent] - Config file content
|
|
21
22
|
* @param {Array} [options.selectedServices] - Selected services to include
|
|
22
|
-
* @param {number} [options.port=
|
|
23
|
+
* @param {number} [options.port=Defaults.DEFAULT_MCP_SERVER_PORT] - Server port
|
|
23
24
|
* @param {Function} [options.onError] - Error callback
|
|
24
25
|
* @param {Function} [options.onReady] - Ready callback
|
|
25
26
|
* @returns {Promise<Object>} Setup result with convertedConfig, updatedConfig, filePath
|
|
26
27
|
*/
|
|
27
28
|
async setup(options = {}) {
|
|
28
|
-
const {
|
|
29
|
+
const {
|
|
30
|
+
filePath,
|
|
31
|
+
fileContent,
|
|
32
|
+
selectedServices,
|
|
33
|
+
port = Defaults.DEFAULT_MCP_SERVER_PORT,
|
|
34
|
+
onError,
|
|
35
|
+
onReady,
|
|
36
|
+
onLog,
|
|
37
|
+
} = options;
|
|
29
38
|
|
|
30
39
|
if (!filePath && !fileContent) {
|
|
31
40
|
return {
|
|
@@ -63,6 +72,7 @@ export class ServerManagementService {
|
|
|
63
72
|
await this.startServer({
|
|
64
73
|
configPath: mcpsJsonPath,
|
|
65
74
|
port,
|
|
75
|
+
onLog,
|
|
66
76
|
onError: (err) => {
|
|
67
77
|
if (onError) {
|
|
68
78
|
onError(err);
|
|
@@ -98,7 +108,13 @@ export class ServerManagementService {
|
|
|
98
108
|
* Start MCP Shark server
|
|
99
109
|
*/
|
|
100
110
|
async startServer(options = {}) {
|
|
101
|
-
const {
|
|
111
|
+
const {
|
|
112
|
+
configPath,
|
|
113
|
+
port = Defaults.DEFAULT_MCP_SERVER_PORT,
|
|
114
|
+
onError,
|
|
115
|
+
onReady,
|
|
116
|
+
onLog,
|
|
117
|
+
} = options;
|
|
102
118
|
|
|
103
119
|
const mcpsJsonPath = configPath || this.configService.getMcpConfigPath();
|
|
104
120
|
|
|
@@ -114,8 +130,13 @@ export class ServerManagementService {
|
|
|
114
130
|
configPath: mcpsJsonPath,
|
|
115
131
|
port,
|
|
116
132
|
auditLogger,
|
|
133
|
+
logger: this.logger,
|
|
134
|
+
onLog,
|
|
117
135
|
onError: (err) => {
|
|
118
|
-
this.logger?.error(
|
|
136
|
+
this.logger?.error(
|
|
137
|
+
{ message: err.message, stack: err.stack, error: err },
|
|
138
|
+
'Failed to start mcp-shark server'
|
|
139
|
+
);
|
|
119
140
|
this.serverInstance = null;
|
|
120
141
|
if (onError) {
|
|
121
142
|
onError(err);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-shark/mcp-shark",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.9",
|
|
4
4
|
"description": "Aggregate multiple Model Context Protocol (MCP) servers into a single unified interface with a powerful monitoring UI. Prov deep visibility into every request and response.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./bin/mcp-shark.js",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"publish:dry-run": "npm publish --dry-run"
|
|
47
47
|
},
|
|
48
48
|
"lint-staged": {
|
|
49
|
-
"
|
|
49
|
+
"*.{js,jsx,ts,tsx,json}": ["biome check --write"]
|
|
50
50
|
},
|
|
51
51
|
"keywords": [
|
|
52
52
|
"mcp",
|