Skip to content

Commit 3875b08

Browse files
authored
Merge pull request #7 from elizaOS/feat/sse-default-mode
feat: Make SSE mode the default transport
2 parents ed6b72f + 6ce4ee9 commit 3875b08

File tree

11 files changed

+108
-59
lines changed

11 files changed

+108
-59
lines changed

CLAUDE.md

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,18 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
1616
## COMMON COMMANDS
1717

1818
```bash
19-
# Development
20-
bun run start # Start the gateway server
21-
bun run dev # Start in development mode
19+
# Development - SSE Mode (default, exposes HTTP/SSE endpoints)
20+
bun run start --config=examples/config.yaml # Start gateway in SSE mode on port 8000
21+
bun run start --config=examples/config.yaml --port=3000 # SSE mode on custom port
22+
bun run dev --config=examples/config.yaml # Development SSE mode
23+
24+
# Development - STDIO Mode (for Claude Desktop integration)
25+
bun run start:stdio --config=examples/config.yaml # Start gateway in STDIO mode
26+
bun run dev:stdio --config=examples/config.yaml # Development STDIO mode
27+
28+
# Direct invocation with mode flags
29+
bun run src/index.ts --config=config.yaml --mode=sse --port=8000 # SSE mode (explicit)
30+
bun run src/index.ts --config=config.yaml --mode=stdio # STDIO mode (explicit)
2231

2332
# Testing
2433
bun run test # Run comprehensive E2E tests
@@ -30,14 +39,14 @@ bun run test:quick # Run fast, essential tests only (recommended during
3039
# Type Checking
3140
bun run type-check # Run TypeScript type checking (no build output)
3241

33-
# Testing with specific configs
34-
npx @modelcontextprotocol/inspector node build/index.js --config=examples/config.yaml
35-
3642
# Testing with paid config
37-
bun run start --config=examples/paid-config.yaml
43+
bun run start --config=examples/paid-config.yaml --port=8000
3844
```
3945

40-
**IMPORTANT:** There is NO build, lint, or compilation step. The project runs directly from TypeScript source files using Bun. Only use `type-check` to validate types.
46+
**IMPORTANT:**
47+
- There is NO build, lint, or compilation step. The project runs directly from TypeScript source files using Bun. Only use `type-check` to validate types.
48+
- **Default mode is SSE** (HTTP/SSE server), which exposes the gateway over HTTP with x402 payment support
49+
- **STDIO mode** is for local integrations (Claude Desktop, Cursor, etc.)
4150

4251
---
4352

@@ -302,14 +311,29 @@ servers:
302311
## IMPORTANT DEVELOPMENT NOTES
303312

304313
### Entry Point
305-
- `src/index.ts` is the CLI entry point
306-
- Accepts `--config=<path>` flag for configuration file
307-
- Falls back to environment variables if no config file provided
308-
- Creates STDIO transport and connects gateway server
309-
310-
### Transport Priority
311-
- STDIO is the default and most common transport (local MCP servers)
312-
- HTTP/SSE/WebSocket are for remote servers (less common)
314+
- `src/index.ts` is the CLI entry point with dual-mode support
315+
- **SSE mode** (default): Runs HTTP/SSE wrapper for network access with x402 payment support
316+
- **STDIO mode**: Direct stdin/stdout for local clients (Claude Desktop, Cursor, etc.)
317+
- Accepts flags:
318+
- `--config=<path>` - Configuration file path (required for SSE mode)
319+
- `--mode=<sse|stdio>` - Transport mode (default: sse)
320+
- `--port=<number>` - Port for SSE mode (default: 8000)
321+
- Falls back to environment variables if no config file provided (STDIO mode only)
322+
323+
### Transport Modes
324+
- **SSE** (default): HTTP/SSE server mode - exposes gateway over HTTP with x402 payment support
325+
- Used for: Network access, agent integrations, payment-gated APIs
326+
- Endpoints: `GET /sse` (Server-Sent Events), `POST /message`
327+
- **STDIO**: Standard input/output mode - direct communication via stdin/stdout
328+
- Used for: Claude Desktop, Cursor, local MCP clients, testing
329+
- Required for all test suites
330+
331+
### Downstream Server Transports
332+
Gateway can connect to downstream servers using:
333+
- **STDIO** - Most common (local MCP servers)
334+
- **HTTP/HTTPS** - Remote MCP servers
335+
- **SSE** - Streaming connections
336+
- **WebSocket** - Bidirectional real-time
313337
- Legacy format auto-converts to STDIO transport
314338

315339
### Logging System

example-agents/run-gateway-sse.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ echo "━━━━━━━━━━━━━━━━━━━━━━━━
1919

2020
cd "$(dirname "$0")/.."
2121

22-
bun run src/transports/http-wrapper.ts \
22+
# SSE is now the default mode, so we can use the main entry point
23+
bun run src/index.ts \
2324
--config=$CONFIG_PATH \
25+
--mode=sse \
2426
--port=$PORT

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
"type": "module",
66
"main": "build/index.js",
77
"scripts": {
8-
"start": "bun run src/index.ts",
9-
"dev": "bun run src/index.ts",
8+
"build": "bun build src/index.ts --outdir build --target node --format esm --sourcemap=external",
9+
"start": "bun run src/index.ts --config=examples/coingecko-config.yaml --mode=sse --port=8000",
10+
"start:stdio": "bun run src/index.ts --config=examples/coingecko-config.yaml --mode=stdio",
11+
"dev": "bun run src/index.ts --mode=sse",
12+
"dev:stdio": "bun run src/index.ts --mode=stdio",
1013
"type-check": "tsc --noEmit",
1114
"test": "bun run tests/e2e-test.ts",
1215
"test:quick": "bun run tests/e2e-simple.ts",

src/index.ts

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,49 @@
33
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
44
import { GatewayServer } from './core/gateway.js';
55
import { configManager } from './config/manager.js';
6+
import { HTTPGatewayWrapper } from './transports/http-wrapper.js';
7+
8+
type TransportMode = 'stdio' | 'sse';
69

710
/**
811
* Main entry point for the MCP Gateway Server
912
*/
1013
async function main(): Promise<void> {
1114
const args = process.argv.slice(2);
1215

16+
// Parse command line arguments
17+
const configFile = args.find(arg => arg.startsWith('--config='))?.replace('--config=', '');
18+
const modeArg = args.find(arg => arg.startsWith('--mode='))?.replace('--mode=', '') as TransportMode | undefined;
19+
const portArg = args.find(arg => arg.startsWith('--port='))?.replace('--port=', '');
20+
21+
// Default to SSE mode
22+
const mode: TransportMode = modeArg || 'sse';
23+
const port = portArg ? parseInt(portArg, 10) : 8000;
24+
1325
// Setup logging based on config (will be overridden by config later)
1426
const logLevel = process.env.MCP_LOG_LEVEL || 'info';
1527
const logger = createLogger(logLevel);
1628

1729
try {
30+
// SSE mode - start HTTP/SSE wrapper
31+
if (mode === 'sse') {
32+
if (!configFile) {
33+
logger.error('SSE mode requires --config flag');
34+
logger.info('Usage: bun run src/index.ts --config=path/to/config.yaml --mode=sse --port=8000');
35+
process.exit(1);
36+
}
37+
38+
logger.info(`Starting MCP Gateway in SSE mode on port ${port}`);
39+
const wrapper = new HTTPGatewayWrapper(configFile, port, logger);
40+
wrapper.start();
41+
return;
42+
}
43+
44+
// STDIO mode - run gateway directly
45+
logger.info('Starting MCP Gateway in STDIO mode');
46+
1847
// Load configuration
1948
let config;
20-
const configFile = args.find(arg => arg.startsWith('--config='))?.replace('--config=', '');
21-
2249
if (configFile) {
2350
logger.info(`Loading configuration from file: ${configFile}`);
2451
config = configManager.loadFromFile(configFile);
@@ -129,8 +156,15 @@ USAGE:
129156
130157
OPTIONS:
131158
--config=<path> Path to configuration file (JSON or YAML)
159+
--mode=<mode> Transport mode: sse (default) or stdio
160+
--port=<port> Port for SSE mode (default: 8000)
132161
--help Show this help message
133162
163+
TRANSPORT MODES:
164+
sse HTTP/SSE server mode - exposes gateway over HTTP with x402 payment support
165+
Endpoints: GET /sse (Server-Sent Events), POST /message
166+
stdio Standard I/O mode - communicates via stdin/stdout (used by Claude Desktop)
167+
134168
ENVIRONMENT VARIABLES:
135169
MCP_GATEWAY_NAME Name of the gateway (default: "MCP Gateway")
136170
MCP_GATEWAY_VERSION Version of the gateway (default: "1.0.0")
@@ -144,11 +178,17 @@ ENVIRONMENT VARIABLES:
144178
MCP_HEALTH_CHECK_INTERVAL Health check interval in ms (default: 60000)
145179
146180
EXAMPLES:
147-
# Run with configuration file
148-
mcp-gateway --config=config.yaml
181+
# Run in SSE mode (default) - HTTP server with x402 payments
182+
mcp-gateway --config=config.yaml --port=8000
183+
184+
# Explicitly specify SSE mode
185+
mcp-gateway --config=config.yaml --mode=sse --port=8000
186+
187+
# Run in STDIO mode (for Claude Desktop integration)
188+
mcp-gateway --config=config.yaml --mode=stdio
149189
150-
# Run with environment variables
151-
MCP_SERVERS="weather:node:weather.js;filesystem:python:fs_server.py" mcp-gateway
190+
# Run with environment variables (STDIO mode)
191+
MCP_SERVERS="weather:node:weather.js;filesystem:python:fs_server.py" mcp-gateway --mode=stdio
152192
153193
For more information, visit: https://github.com/studio/mcp-gateway
154194
`);

src/transports/http-wrapper.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,17 @@ class HTTPGatewayWrapper {
4545

4646
// Gateway subprocess command - use wrapper config (no payment checking in subprocess)
4747
// The HTTP layer handles all payment verification
48-
const gatewayPath = new URL('../index.ts', import.meta.url).pathname;
48+
// IMPORTANT: Subprocess must run in STDIO mode, not SSE mode
49+
50+
// Just re-run the same script we're currently running, but in STDIO mode
51+
// process.argv[1] is the path to the currently executing script (works for both src and build)
52+
// Use 'bun' directly (not 'bun run') to execute the script without package.json interference
53+
const currentScript = process.argv[1]!;
4954
const wrapperConfigPath = configPath.replace('.yaml', '-wrapper.yaml').replace('.json', '-wrapper.json');
50-
this.gatewayCommand = ['bun', 'run', gatewayPath, `--config=${wrapperConfigPath}`];
55+
this.gatewayCommand = ['bun', currentScript, '--mode=stdio', `--config=${wrapperConfigPath}`];
5156

5257
logger.info(`Gateway subprocess will use config: ${wrapperConfigPath}`);
58+
logger.info(`Gateway subprocess command: ${this.gatewayCommand.join(' ')}`);
5359
}
5460

5561
start(): void {

tests/e2e-passthrough.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ class PassthroughE2ETestRunner {
246246
const gatewayProcess: ChildProcess = spawn('bun', [
247247
'run',
248248
'src/index.ts',
249+
'--mode=stdio',
249250
`--config=${configPath}`
250251
], {
251252
cwd: process.cwd(),

tests/e2e-payment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ class PaymentE2ETestRunner {
282282

283283
private async runGatewayWithTimeout(configPath: string, timeoutMs: number = 8000): Promise<string> {
284284
return new Promise((resolve, reject) => {
285-
const args = ['run', 'src/index.ts', `--config=${configPath}`];
285+
const args = ['run', 'src/index.ts', '--mode=stdio', `--config=${configPath}`];
286286

287287
const gatewayProcess = spawn('bun', args, {
288288
stdio: ['pipe', 'pipe', 'pipe'],

tests/e2e-simple.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ class SimpleE2ETestRunner {
236236

237237
private async runGatewayWithTimeout(configPath?: string, timeoutMs: number = 5000): Promise<string> {
238238
return new Promise((resolve, reject) => {
239-
const args = ['run', 'src/index.ts'];
239+
const args = ['run', 'src/index.ts', '--mode=stdio'];
240240
if (configPath) {
241241
args.push(`--config=${configPath}`);
242242
}

tests/e2e-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ class E2ETestRunner {
301301
}
302302

303303
private async startGateway(configPath?: string): Promise<void> {
304-
const args = ['run', 'src/index.ts'];
304+
const args = ['run', 'src/index.ts', '--mode=stdio'];
305305
if (configPath) {
306306
args.push(`--config=${configPath}`);
307307
}

tests/test-runner.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ test_config() {
9090
echo "Press Ctrl+C to stop..."
9191
echo ""
9292

93-
bun run src/index.ts --config="tests/configs/$config_file"
93+
bun run src/index.ts --mode=stdio --config="tests/configs/$config_file"
9494
}
9595

9696
# Function to run manual testing with inspector
@@ -110,7 +110,7 @@ run_manual_test() {
110110
echo "This will open the MCP Inspector for interactive testing..."
111111
echo ""
112112

113-
bun x @modelcontextprotocol/inspector bun run src/index.ts --config="tests/configs/$config_file"
113+
bun x @modelcontextprotocol/inspector bun run src/index.ts --mode=stdio --config="tests/configs/$config_file"
114114
}
115115

116116
# Function to show usage

0 commit comments

Comments
 (0)