@mcp-shark/mcp-shark 1.4.0 → 1.4.1

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 (3) hide show
  1. package/README.md +16 -1
  2. package/bin/mcp-shark.js +179 -53
  3. package/package.json +4 -2
package/README.md CHANGED
@@ -66,11 +66,20 @@ Or if installed locally:
66
66
  npx @mcp-shark/mcp-shark
67
67
  ```
68
68
 
69
+ **Open browser automatically:**
70
+
71
+ ```bash
72
+ mcp-shark --open
73
+ # or
74
+ mcp-shark -o
75
+ ```
76
+
69
77
  The UI will automatically:
70
78
 
71
79
  - Install dependencies if needed
72
80
  - Build the frontend if needed
73
81
  - Start the server on `http://localhost:9853`
82
+ - Optionally open your browser automatically (with `--open` flag)
74
83
 
75
84
  Open your browser to `http://localhost:9853` to access the MCP Shark interface.
76
85
 
@@ -331,8 +340,14 @@ No manual configuration editing required - MCP Shark handles everything for you.
331
340
  mcp-shark
332
341
  ```
333
342
 
343
+ Or to automatically open your browser:
344
+
345
+ ```bash
346
+ mcp-shark --open
347
+ ```
348
+
334
349
  3. **Open your browser:**
335
- Navigate to `http://localhost:9853`
350
+ Navigate to `http://localhost:9853` (or it will open automatically with `--open` flag)
336
351
 
337
352
  4. **Interactive Tour**: On first launch, you'll see an interactive tour - follow it to get started
338
353
 
package/bin/mcp-shark.js CHANGED
@@ -3,67 +3,110 @@
3
3
  import { spawn } from 'node:child_process';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { dirname, join, resolve } from 'node:path';
6
- import { existsSync } from 'node:fs';
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { Command } from 'commander';
8
+ import open from 'open';
9
+
10
+ const SERVER_URL = 'http://localhost:9853';
11
+ const BROWSER_OPEN_DELAY = 1000;
7
12
 
8
13
  const __filename = fileURLToPath(import.meta.url);
9
14
  const __dirname = dirname(__filename);
10
15
  const rootDir = resolve(__dirname, '..');
11
- const uiDir = join(rootDir, 'ui');
12
16
 
13
- // Check if UI directory exists
14
- if (!existsSync(uiDir)) {
15
- console.error('Error: UI directory not found. Please ensure you are in the correct directory.');
16
- process.exit(1);
17
- }
17
+ /**
18
+ * Display welcome banner
19
+ */
20
+ function displayWelcomeBanner() {
21
+ const pkgPath = join(rootDir, 'package.json');
22
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
23
+ const version = pkg.version;
18
24
 
19
- // Check if node_modules exists in root directory
20
- const rootNodeModules = join(rootDir, 'node_modules');
21
- if (!existsSync(rootNodeModules)) {
22
- console.log('Installing dependencies...');
23
- const installProcess = spawn('npm', ['install'], {
24
- cwd: rootDir,
25
- stdio: 'inherit',
26
- shell: true,
27
- });
25
+ const banner = `
26
+ ███╗ ███╗ ██████╗ ██████╗ ███████╗██╗ ██╗ █████╗ ██████╗ ██╗ ██╗
27
+ ████╗ ████║██╔════╝██╔══██╗ ██╔════╝██║ ██║██╔══██╗██╔══██╗██║ ██╔╝
28
+ ██╔████╔██║██║ ██████╔╝ ███████╗███████║███████║██████╔╝█████╔╝
29
+ ██║╚██╔╝██║██║ ██╔═══╝ ╚════██║██╔══██║██╔══██║██╔══██╗██╔═██╗
30
+ ██║ ╚═╝ ██║╚██████╗██║ ███████║██║ ██║██║ ██║██║ ██║██║ ██╗
31
+ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝
32
+
33
+ Aggregate multiple MCP servers into a unified interface
34
+ Version: ${version} | Homepage: https://mcpshark.sh
35
+ `;
28
36
 
29
- installProcess.on('close', (code) => {
30
- if (code !== 0) {
31
- console.error('Failed to install dependencies');
32
- process.exit(1);
33
- }
34
- console.log('Dependencies installed successfully!\n');
35
- startUIServer();
36
- });
37
- } else {
38
- startUIServer();
37
+ console.log(banner);
39
38
  }
40
39
 
41
- function startUIServer() {
42
- // Check if dist directory exists (production build)
43
- const distDir = join(uiDir, 'dist');
44
- if (!existsSync(distDir)) {
45
- console.log('Building UI for production...');
46
- const buildProcess = spawn('npm', ['run', 'build'], {
47
- cwd: uiDir,
40
+ const uiDir = join(rootDir, 'ui');
41
+ const distDir = join(uiDir, 'dist');
42
+ const rootNodeModules = join(rootDir, 'node_modules');
43
+
44
+ /**
45
+ * Run a command and return a promise that resolves when it completes
46
+ */
47
+ function runCommand(command, args, options) {
48
+ return new Promise((resolve, reject) => {
49
+ const process = spawn(command, args, {
50
+ ...options,
48
51
  stdio: 'inherit',
49
52
  shell: true,
50
53
  });
51
54
 
52
- buildProcess.on('close', (code) => {
55
+ process.on('close', (code) => {
53
56
  if (code !== 0) {
54
- console.error('Failed to build UI');
55
- process.exit(1);
57
+ reject(new Error(`Command failed with exit code ${code}`));
58
+ } else {
59
+ resolve();
56
60
  }
57
- runServer();
58
61
  });
59
- } else {
60
- runServer();
62
+
63
+ process.on('error', (error) => {
64
+ reject(error);
65
+ });
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Install dependencies in the root directory
71
+ */
72
+ async function installDependencies() {
73
+ console.log('Installing dependencies...');
74
+ try {
75
+ await runCommand('npm', ['install'], { cwd: rootDir });
76
+ console.log('Dependencies installed successfully!\n');
77
+ } catch (error) {
78
+ console.error('Failed to install dependencies:', error.message);
79
+ process.exit(1);
61
80
  }
62
81
  }
63
82
 
64
- function runServer() {
83
+ /**
84
+ * Build the UI for production
85
+ */
86
+ async function buildUI() {
87
+ console.log('Building UI for production...');
88
+ try {
89
+ await runCommand('npm', ['run', 'build'], { cwd: uiDir });
90
+ } catch (error) {
91
+ console.error('Failed to build UI:', error.message);
92
+ process.exit(1);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Open the browser after a short delay
98
+ */
99
+ async function openBrowser() {
100
+ await new Promise((resolve) => setTimeout(resolve, BROWSER_OPEN_DELAY));
101
+ open(SERVER_URL);
102
+ }
103
+
104
+ /**
105
+ * Start the UI server
106
+ */
107
+ async function startServer(shouldOpenBrowser = false) {
65
108
  console.log('Starting MCP Shark UI server...');
66
- console.log('Open http://localhost:9853 in your browser');
109
+ console.log(`Open ${SERVER_URL} in your browser`);
67
110
  console.log('Press Ctrl+C to stop\n');
68
111
 
69
112
  const serverProcess = spawn('node', ['server.js'], {
@@ -72,22 +115,105 @@ function runServer() {
72
115
  shell: true,
73
116
  });
74
117
 
118
+ if (shouldOpenBrowser) {
119
+ await openBrowser();
120
+ }
121
+
122
+ let isShuttingDown = false;
123
+
75
124
  serverProcess.on('close', (code) => {
76
- if (code !== 0 && code !== null) {
77
- console.error(`Server exited with code ${code}`);
78
- process.exit(code);
125
+ if (!isShuttingDown) {
126
+ if (code !== 0 && code !== null) {
127
+ console.error(`Server exited with code ${code}`);
128
+ process.exit(code);
129
+ }
130
+ } else {
131
+ process.exit(0);
79
132
  }
80
133
  });
81
134
 
82
135
  // Handle process termination
83
- process.on('SIGINT', () => {
84
- console.log('\nShutting down...');
85
- serverProcess.kill('SIGINT');
86
- process.exit(0);
87
- });
136
+ const shutdown = async (signal) => {
137
+ if (isShuttingDown) return;
138
+ isShuttingDown = true;
88
139
 
89
- process.on('SIGTERM', () => {
90
- serverProcess.kill('SIGTERM');
91
- process.exit(0);
92
- });
140
+ console.log('Shutting down...');
141
+
142
+ // Send signal to child process
143
+ serverProcess.kill(signal);
144
+
145
+ // Wait for child process to exit, with timeout
146
+ const timeout = setTimeout(() => {
147
+ console.log('Forcefully terminating server process...');
148
+ serverProcess.kill('SIGKILL');
149
+ process.exit(1);
150
+ }, 5000);
151
+
152
+ serverProcess.once('close', () => {
153
+ clearTimeout(timeout);
154
+ process.exit(0);
155
+ });
156
+ };
157
+
158
+ process.on('SIGINT', () => shutdown('SIGINT'));
159
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
160
+ }
161
+
162
+ /**
163
+ * Validate that required directories exist
164
+ */
165
+ function validateDirectories() {
166
+ if (!existsSync(uiDir)) {
167
+ console.error('Error: UI directory not found. Please ensure you are in the correct directory.');
168
+ process.exit(1);
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Ensure dependencies are installed
174
+ */
175
+ async function ensureDependencies() {
176
+ if (!existsSync(rootNodeModules)) {
177
+ await installDependencies();
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Ensure UI is built
183
+ */
184
+ async function ensureUIBuilt() {
185
+ if (!existsSync(distDir)) {
186
+ await buildUI();
187
+ }
93
188
  }
189
+
190
+ /**
191
+ * Main execution function
192
+ */
193
+ async function main() {
194
+ // Display welcome banner
195
+ displayWelcomeBanner();
196
+
197
+ // Parse command line options
198
+ const program = new Command();
199
+ program.option('-o, --open', 'Open the browser', false).parse(process.argv);
200
+
201
+ const options = program.opts();
202
+
203
+ // Validate environment
204
+ validateDirectories();
205
+
206
+ // Ensure dependencies are installed
207
+ await ensureDependencies();
208
+
209
+ // Ensure UI is built
210
+ await ensureUIBuilt();
211
+
212
+ // Start the server
213
+ await startServer(options.open);
214
+ }
215
+
216
+ main().catch((error) => {
217
+ console.error('Unexpected error:', error);
218
+ process.exit(1);
219
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-shark/mcp-shark",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
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",
@@ -85,7 +85,9 @@
85
85
  "mcp-shark-common": "github:mcp-shark/mcp-shark-common",
86
86
  "react": "^18.2.0",
87
87
  "react-dom": "^18.2.0",
88
- "ws": "^8.16.0"
88
+ "ws": "^8.16.0",
89
+ "open": "^11.0.0",
90
+ "commander": "^14.0.2"
89
91
  },
90
92
  "devDependencies": {
91
93
  "@commitlint/cli": "^19.5.0",