Skip to content

Commit 9b23ff3

Browse files
tianzhouclaude
andauthored
fix: only run http server/admin console if --transport http (#144)
* docs: add design for disabling HTTP server in stdio mode Addresses issue #143 where stdio transport still starts HTTP server on port 8080, causing conflicts when running multiple instances. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * fix: disable HTTP server when using stdio transport Fixes #143 - Multiple DBHub instances with stdio transport can now run simultaneously without port conflicts. Changes: - HTTP server (admin console, /mcp endpoint, API) only starts in http mode - stdio mode is now pure MCP-over-stdio with no network listeners - Updated console output to show helpful connection message for stdio users 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * docs: update transport option documentation Clarifies the behavior difference between stdio and http transports: - stdio: pure MCP-over-stdio, no HTTP server, allows multiple instances - http: HTTP server with admin console, MCP endpoint, and APIs Related to #143 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * chore: remove plan * chore: minor tweak --------- Co-authored-by: Claude <[email protected]>
1 parent 60dd90d commit 9b23ff3

File tree

2 files changed

+79
-74
lines changed

2 files changed

+79
-74
lines changed

docs/config/server-options.mdx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,22 +152,31 @@ title: "Server Options"
152152
Transport protocol for MCP communication.
153153

154154
**Options:**
155-
- `stdio` - For desktop tools (Claude Desktop, Claude Code, Cursor with stdio config)
156-
- `http` - For browser and network clients, streamable HTTP transport
155+
- `stdio` - For desktop tools (Claude Desktop, Claude Code, Cursor). Pure MCP-over-stdio with no HTTP server.
156+
- `http` - For browser and network clients. Starts HTTP server with MCP endpoint, admin console, and API.
157+
158+
| Feature | `stdio` | `http` |
159+
|---------|:-------:|:------:|
160+
| MCP communication | stdin/stdout | HTTP POST `/mcp` |
161+
| Admin console ||`http://host:port/` |
162+
| Health check ||`/healthz` |
163+
| API ||`/api/xxx` |
164+
| Multiple instances | ✅ No port conflicts | Requires different ports |
157165

158166
```bash
159-
# stdio (default) - for Claude Desktop, Claude Code
167+
# stdio (default) - for Claude Desktop, Claude Code, Cursor
168+
# No HTTP server started
160169
npx @bytebase/dbhub --transport stdio --dsn "..."
161170

162-
# http - for web clients and remote access
171+
# http - for web clients, admin console, and remote access
163172
npx @bytebase/dbhub --transport http --port 8080 --dsn "..."
164173
```
165174
</ParamField>
166175

167176
### port
168177

169178
<ParamField path="--port" type="number" env="PORT" default="8080">
170-
HTTP server port. Only applicable when using `--transport=http`.
179+
HTTP server port. Only used when `--transport=http`. Ignored for stdio transport.
171180

172181
```bash
173182
npx @bytebase/dbhub --transport http --port 3000 --dsn "..."

src/server.ts

Lines changed: 65 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,12 @@ See documentation for more details on configuring database connections.
124124
// Resolve transport type (for MCP server)
125125
const transportData = resolveTransport();
126126
console.error(`MCP transport: ${transportData.type}`);
127-
console.error(`Transport source: ${transportData.source}`);
128127

129-
// Resolve port for HTTP server (always start HTTP for frontend)
130-
const portData = resolvePort();
131-
const port = portData.port;
132-
console.error(`HTTP server port: ${port} (source: ${portData.source})`);
128+
// Resolve port for HTTP server (only needed for http transport)
129+
const portData = transportData.type === "http" ? resolvePort() : null;
130+
if (portData) {
131+
console.error(`HTTP server port: ${portData.port} (source: ${portData.source})`);
132+
}
133133

134134
// Print ASCII art banner with version and slogan
135135
const readonly = isReadOnlyMode();
@@ -161,46 +161,47 @@ See documentation for more details on configuring database connections.
161161

162162
console.error(generateBanner(SERVER_VERSION, activeModes));
163163

164-
// Always set up Express server for frontend (regardless of MCP transport)
165-
const app = express();
166-
167-
// Enable JSON parsing
168-
app.use(express.json());
169-
170-
// Handle CORS and security headers
171-
app.use((req, res, next) => {
172-
// Validate Origin header to prevent DNS rebinding attacks
173-
const origin = req.headers.origin;
174-
if (origin && !origin.startsWith('http://localhost') && !origin.startsWith('https://localhost')) {
175-
return res.status(403).json({ error: 'Forbidden origin' });
176-
}
177-
178-
res.header('Access-Control-Allow-Origin', origin || 'http://localhost');
179-
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
180-
res.header('Access-Control-Allow-Headers', 'Content-Type, Mcp-Session-Id');
181-
res.header('Access-Control-Allow-Credentials', 'true');
182-
183-
if (req.method === 'OPTIONS') {
184-
return res.sendStatus(200);
185-
}
186-
next();
187-
});
188-
189-
// Serve static frontend files
190-
const frontendPath = path.join(__dirname, "public");
191-
app.use(express.static(frontendPath));
192-
193-
// Health check endpoint
194-
app.get("/healthz", (req, res) => {
195-
res.status(200).send("OK");
196-
});
197-
198-
// Data sources API endpoints
199-
app.get("/api/sources", listSources);
200-
app.get("/api/sources/:sourceId", getSource);
201-
202-
// Set up MCP endpoint if using HTTP transport
164+
// Set up transport-specific server
203165
if (transportData.type === "http") {
166+
// HTTP transport: Start Express server with MCP endpoint and admin console
167+
const port = portData!.port;
168+
const app = express();
169+
170+
// Enable JSON parsing
171+
app.use(express.json());
172+
173+
// Handle CORS and security headers
174+
app.use((req, res, next) => {
175+
// Validate Origin header to prevent DNS rebinding attacks
176+
const origin = req.headers.origin;
177+
if (origin && !origin.startsWith('http://localhost') && !origin.startsWith('https://localhost')) {
178+
return res.status(403).json({ error: 'Forbidden origin' });
179+
}
180+
181+
res.header('Access-Control-Allow-Origin', origin || 'http://localhost');
182+
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
183+
res.header('Access-Control-Allow-Headers', 'Content-Type, Mcp-Session-Id');
184+
res.header('Access-Control-Allow-Credentials', 'true');
185+
186+
if (req.method === 'OPTIONS') {
187+
return res.sendStatus(200);
188+
}
189+
next();
190+
});
191+
192+
// Serve static frontend files
193+
const frontendPath = path.join(__dirname, "public");
194+
app.use(express.static(frontendPath));
195+
196+
// Health check endpoint
197+
app.get("/healthz", (req, res) => {
198+
res.status(200).send("OK");
199+
});
200+
201+
// Data sources API endpoints
202+
app.get("/api/sources", listSources);
203+
app.get("/api/sources/:sourceId", getSource);
204+
204205
// Main endpoint for streamable HTTP transport
205206
app.post("/mcp", async (req, res) => {
206207
try {
@@ -222,36 +223,31 @@ See documentation for more details on configuring database connections.
222223
}
223224
}
224225
});
225-
}
226226

227-
// SPA fallback - serve index.html for all non-API routes
228-
app.get("*", (req, res) => {
229-
res.sendFile(path.join(frontendPath, "index.html"));
230-
});
231-
232-
// Start the HTTP server (always for frontend)
233-
app.listen(port, '0.0.0.0', () => {
234-
// In development mode, suggest using the Vite dev server for hot reloading
235-
if (process.env.NODE_ENV === 'development') {
236-
console.error('🚀 Development mode detected!');
237-
console.error(' Admin console dev server (with HMR): http://localhost:5173');
238-
console.error(' Backend API: http://localhost:8080');
239-
console.error('');
240-
} else {
241-
console.error(`Admin console at http://0.0.0.0:${port}/`);
242-
}
243-
244-
if (transportData.type === "http") {
245-
console.error(`MCP server endpoint at http://0.0.0.0:${port}/mcp`);
246-
}
247-
});
227+
// SPA fallback - serve index.html for all non-API routes
228+
app.get("*", (req, res) => {
229+
res.sendFile(path.join(frontendPath, "index.html"));
230+
});
248231

249-
// If using STDIO transport, also set up STDIO connection for MCP
250-
if (transportData.type === "stdio") {
232+
// Start the HTTP server
233+
app.listen(port, '0.0.0.0', () => {
234+
// In development mode, suggest using the Vite dev server for hot reloading
235+
if (process.env.NODE_ENV === 'development') {
236+
console.error('Development mode detected!');
237+
console.error(' Admin console dev server (with HMR): http://localhost:5173');
238+
console.error(' Backend API: http://localhost:8080');
239+
console.error('');
240+
} else {
241+
console.error(`Admin console at http://0.0.0.0:${port}/`);
242+
}
243+
console.error(`MCP server endpoint at http://0.0.0.0:${port}/mcp`);
244+
});
245+
} else {
246+
// STDIO transport: Pure MCP-over-stdio, no HTTP server
251247
const server = createServer();
252248
const transport = new StdioServerTransport();
253-
console.error("Starting MCP with STDIO transport");
254249
await server.connect(transport);
250+
console.error("MCP server running on stdio");
255251

256252
// Listen for SIGINT to gracefully shut down
257253
process.on("SIGINT", async () => {

0 commit comments

Comments
 (0)