cellium-mcp-client 1.1.7 → 2.0.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.
- package/README.md +101 -21
- package/dist/cli.js +16 -5
- package/dist/client.d.ts +3 -0
- package/dist/client.js +160 -89
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# @cellium/mcp-client
|
|
2
2
|
|
|
3
|
-
A Model Context Protocol (MCP) server that
|
|
3
|
+
A Model Context Protocol (MCP) server that provides both HTTP streaming and stdio transports to connect remote Cellium processor servers with MCP clients like Codex.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **
|
|
7
|
+
- **Dual Transport Support**: Both HTTP streaming and stdio transport for maximum compatibility
|
|
8
|
+
- **Auto-Detection**: Automatically chooses appropriate transport based on execution context
|
|
8
9
|
- **Authentication**: Token-based authentication with format `user:username:hash`
|
|
9
10
|
- **Robust Connection Management**: Automatic reconnection with configurable retry logic
|
|
10
11
|
- **MCP Protocol Compliance**: Uses official `@modelcontextprotocol/sdk`
|
|
@@ -27,6 +28,9 @@ npx @cellium/mcp-client
|
|
|
27
28
|
|
|
28
29
|
### Command Line Interface
|
|
29
30
|
|
|
31
|
+
#### HTTP Streaming Mode (Default)
|
|
32
|
+
Perfect for direct connections to the MCP server:
|
|
33
|
+
|
|
30
34
|
```bash
|
|
31
35
|
# Using environment variable for token
|
|
32
36
|
export CELLIUM_MCP_TOKEN="user:your-username:your-hash"
|
|
@@ -35,19 +39,46 @@ cellium-mcp-client
|
|
|
35
39
|
# Using command line option
|
|
36
40
|
cellium-mcp-client --token "user:your-username:your-hash"
|
|
37
41
|
|
|
38
|
-
# With custom endpoint and verbose logging
|
|
42
|
+
# With custom endpoint, port, and verbose logging
|
|
39
43
|
cellium-mcp-client \
|
|
40
44
|
--token "user:your-username:your-hash" \
|
|
41
|
-
--endpoint "https://mcp.cellium.dev/
|
|
45
|
+
--endpoint "https://mcp.cellium.dev/http-stream" \
|
|
46
|
+
--port 3001 \
|
|
42
47
|
--verbose \
|
|
43
48
|
--retry-attempts 5 \
|
|
44
49
|
--retry-delay 2000
|
|
45
50
|
```
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
#### Stdio Mode (Command-Based MCP Clients)
|
|
53
|
+
Perfect for command-based MCP clients like Codex:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Explicit stdio mode
|
|
57
|
+
cellium-mcp-client --stdio --token "user:your-username:your-hash"
|
|
58
|
+
|
|
59
|
+
# Auto-detection when run by MCP clients (stdin is piped)
|
|
60
|
+
# This happens automatically when launched via MCP client configuration
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Configuration with Codex/Other MCP Clients
|
|
64
|
+
|
|
65
|
+
#### Option 1: Direct HTTP Connection (Recommended for modern clients)
|
|
66
|
+
```toml
|
|
67
|
+
[mcp_servers.cellium]
|
|
68
|
+
url = "http://localhost:3001"
|
|
69
|
+
enabled = true
|
|
70
|
+
```
|
|
48
71
|
|
|
49
|
-
|
|
72
|
+
#### Option 2: Command-Based with Stdio Transport (Universal compatibility)
|
|
73
|
+
```toml
|
|
74
|
+
[mcp_servers.cellium]
|
|
75
|
+
command = "npx"
|
|
76
|
+
args = ["-y", "@cellium/mcp-client", "--stdio"]
|
|
77
|
+
env = { CELLIUM_MCP_TOKEN = "user:your-username:your-hash" }
|
|
78
|
+
enabled = true
|
|
79
|
+
```
|
|
50
80
|
|
|
81
|
+
#### Option 3: Command-Based with HTTP Auto-Start
|
|
51
82
|
```toml
|
|
52
83
|
[mcp_servers.cellium]
|
|
53
84
|
command = "npx"
|
|
@@ -64,17 +95,30 @@ import pino from 'pino';
|
|
|
64
95
|
|
|
65
96
|
const logger = pino();
|
|
66
97
|
|
|
67
|
-
|
|
98
|
+
// HTTP Streaming Mode
|
|
99
|
+
const httpClient = new CelliumMCPClient({
|
|
100
|
+
token: 'user:your-username:your-hash',
|
|
101
|
+
endpoint: 'https://mcp.cellium.dev/http-stream',
|
|
102
|
+
httpPort: 3001,
|
|
103
|
+
logger,
|
|
104
|
+
retryAttempts: 3,
|
|
105
|
+
retryDelay: 1000,
|
|
106
|
+
useStdio: false
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Stdio Mode
|
|
110
|
+
const stdioClient = new CelliumMCPClient({
|
|
68
111
|
token: 'user:your-username:your-hash',
|
|
69
|
-
endpoint: 'https://mcp.cellium.dev/
|
|
112
|
+
endpoint: 'https://mcp.cellium.dev/http-stream',
|
|
70
113
|
logger,
|
|
71
114
|
retryAttempts: 3,
|
|
72
|
-
retryDelay: 1000
|
|
115
|
+
retryDelay: 1000,
|
|
116
|
+
useStdio: true
|
|
73
117
|
});
|
|
74
118
|
|
|
75
119
|
await client.connect();
|
|
120
|
+
await client.serve();
|
|
76
121
|
|
|
77
|
-
// The client will now proxy MCP requests to the remote server
|
|
78
122
|
// Handle shutdown gracefully
|
|
79
123
|
process.on('SIGINT', async () => {
|
|
80
124
|
await client.disconnect();
|
|
@@ -87,11 +131,35 @@ process.on('SIGINT', async () => {
|
|
|
87
131
|
| Option | Environment Variable | Description | Default |
|
|
88
132
|
|--------|---------------------|-------------|---------|
|
|
89
133
|
| `--token` | `CELLIUM_MCP_TOKEN` | Authentication token (required) | - |
|
|
90
|
-
| `--endpoint` | - | Server endpoint URL | `
|
|
134
|
+
| `--endpoint` | - | Server endpoint URL | `http://localhost:3000/http-stream` |
|
|
135
|
+
| `--port` | - | HTTP streaming port | `3001` |
|
|
136
|
+
| `--stdio` | - | Force stdio transport | Auto-detected |
|
|
91
137
|
| `--verbose` | - | Enable verbose logging | `false` |
|
|
138
|
+
| `--debug` | - | Enable debug mode with extensive logging | `false` |
|
|
92
139
|
| `--retry-attempts` | - | Number of retry attempts | `3` |
|
|
93
140
|
| `--retry-delay` | - | Delay between retries (ms) | `1000` |
|
|
94
141
|
|
|
142
|
+
## Transport Modes
|
|
143
|
+
|
|
144
|
+
### HTTP Streaming Transport
|
|
145
|
+
- **Use case**: Direct connections from modern MCP clients
|
|
146
|
+
- **Benefits**: Persistent connections, multiple concurrent sessions, better scalability
|
|
147
|
+
- **Connection**: Client connects directly to `http://localhost:3001` (or configured port)
|
|
148
|
+
- **Communication**: Server-Sent Events (SSE) for real-time streaming
|
|
149
|
+
|
|
150
|
+
### Stdio Transport
|
|
151
|
+
- **Use case**: Command-based MCP clients (like traditional Codex configurations)
|
|
152
|
+
- **Benefits**: Universal compatibility, no port conflicts, standard MCP pattern
|
|
153
|
+
- **Connection**: MCP client launches process and communicates via stdin/stdout
|
|
154
|
+
- **Communication**: Standard input/output pipes
|
|
155
|
+
|
|
156
|
+
### Auto-Detection Logic
|
|
157
|
+
The client automatically chooses the appropriate transport:
|
|
158
|
+
|
|
159
|
+
1. **Explicit stdio**: `--stdio` flag forces stdio transport
|
|
160
|
+
2. **Auto-detected stdio**: When stdin is piped (command-based execution)
|
|
161
|
+
3. **Default HTTP**: All other cases use HTTP streaming transport
|
|
162
|
+
|
|
95
163
|
## Authentication Token Format
|
|
96
164
|
|
|
97
165
|
The authentication token must follow the format: `user:username:hash`
|
|
@@ -106,16 +174,17 @@ Example: `user:john-doe:a1b2c3d4e5f6...`
|
|
|
106
174
|
## Architecture
|
|
107
175
|
|
|
108
176
|
```
|
|
109
|
-
┌─────────────────┐
|
|
110
|
-
│
|
|
111
|
-
│
|
|
112
|
-
│ AI Assistant │
|
|
113
|
-
│ (Cody, etc.) │
|
|
114
|
-
└─────────────────┘
|
|
177
|
+
┌─────────────────┐ HTTP/Stdio ┌─────────────────┐ HTTPS ┌─────────────────┐
|
|
178
|
+
│ │◄──────────────►│ │◄──────────►│ │
|
|
179
|
+
│ Codex / │ Streaming │ cellium-mcp- │ HTTP │ Cellium Server │
|
|
180
|
+
│ AI Assistant │ or Pipes │ client (Proxy) │ Requests │ (Remote) │
|
|
181
|
+
│ (Cody, etc.) │ MCP Protocol │ │ │ │
|
|
182
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
115
183
|
```
|
|
116
184
|
|
|
117
|
-
The client acts as
|
|
118
|
-
- **
|
|
185
|
+
The client acts as a **dual-transport MCP server** that proxies requests between MCP clients and the remote Cellium processor server, handling:
|
|
186
|
+
- **HTTP Streaming Transport**: Provides streamable HTTP interface for direct client connection
|
|
187
|
+
- **Stdio Transport**: Traditional stdin/stdout communication for command-based clients
|
|
119
188
|
- **Authentication**: Token-based authentication with remote server
|
|
120
189
|
- **Request Proxying**: Converts MCP requests to HTTP requests to remote server
|
|
121
190
|
- **Error Handling**: Graceful handling of connection failures
|
|
@@ -144,8 +213,9 @@ npm run dev
|
|
|
144
213
|
### Connection Issues
|
|
145
214
|
|
|
146
215
|
1. **Invalid token format**: Ensure your token follows the `user:username:hash` format
|
|
147
|
-
2. **Network connectivity**: Check if `https://mcp.cellium.dev/
|
|
216
|
+
2. **Network connectivity**: Check if `https://mcp.cellium.dev/http-stream` is accessible
|
|
148
217
|
3. **Authentication failed**: Verify your token is valid and not expired
|
|
218
|
+
4. **Port already in use**: Change the `--port` option if port 3001 is already in use
|
|
149
219
|
|
|
150
220
|
### Verbose Logging
|
|
151
221
|
|
|
@@ -159,8 +229,18 @@ cellium-mcp-client --verbose --token "your-token"
|
|
|
159
229
|
|
|
160
230
|
- `Authentication token required`: Set `CELLIUM_MCP_TOKEN` or use `--token`
|
|
161
231
|
- `Invalid token format`: Check token follows `user:username:hash` pattern
|
|
162
|
-
- `Not connected to remote server`: Connection to
|
|
232
|
+
- `Not connected to remote server`: Connection to HTTP streaming endpoint failed
|
|
163
233
|
- `Request timeout`: Remote server didn't respond within 30 seconds
|
|
234
|
+
- `EADDRINUSE`: Port is already in use, try a different `--port`
|
|
235
|
+
|
|
236
|
+
### Connecting with Codex
|
|
237
|
+
|
|
238
|
+
Make sure to connect your MCP client to the HTTP streaming server at:
|
|
239
|
+
```
|
|
240
|
+
http://localhost:3001
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The server provides Server-Sent Events (SSE) streaming for real-time communication.
|
|
164
244
|
|
|
165
245
|
## License
|
|
166
246
|
|
package/dist/cli.js
CHANGED
|
@@ -11,9 +11,11 @@ const program = new commander_1.Command();
|
|
|
11
11
|
program
|
|
12
12
|
.name('cellium-mcp-client')
|
|
13
13
|
.description('MCP client for connecting to remote Cellium processor server')
|
|
14
|
-
.version('
|
|
14
|
+
.version('2.0.0')
|
|
15
15
|
.option('-t, --token <token>', 'Authentication token (format: user:username:hash)')
|
|
16
|
-
.option('-e, --endpoint <url>', 'Server endpoint URL', 'http://localhost:3000/
|
|
16
|
+
.option('-e, --endpoint <url>', 'Server endpoint URL', 'http://localhost:3000/http-stream')
|
|
17
|
+
.option('-p, --port <number>', 'HTTP streaming port for MCP client', '3001')
|
|
18
|
+
.option('--stdio', 'Use stdio transport (for command-based MCP clients)')
|
|
17
19
|
.option('-v, --verbose', 'Enable verbose logging')
|
|
18
20
|
.option('-d, --debug', 'Enable debug mode with extensive logging')
|
|
19
21
|
.option('-r, --retry-attempts <num>', 'Number of retry attempts on connection failure', '3')
|
|
@@ -50,24 +52,33 @@ async function main() {
|
|
|
50
52
|
logger.info('Example: user:myusername:abc123def456...');
|
|
51
53
|
process.exit(1);
|
|
52
54
|
}
|
|
55
|
+
// Auto-detect transport mode
|
|
56
|
+
// Use stdio if explicitly requested or if stdin is piped (indicating command-based usage)
|
|
57
|
+
const useStdio = options.stdio || (!process.stdin.isTTY && process.stdout.isTTY);
|
|
58
|
+
const transportMode = useStdio ? 'stdio' : 'http-streaming';
|
|
53
59
|
logger.info({
|
|
54
|
-
version: '
|
|
60
|
+
version: '2.0.0',
|
|
55
61
|
debugMode: !!options.debug,
|
|
56
|
-
verboseMode: !!options.verbose
|
|
62
|
+
verboseMode: !!options.verbose,
|
|
63
|
+
transportMode
|
|
57
64
|
}, 'Starting Cellium MCP Client');
|
|
58
65
|
logger.debug({
|
|
59
66
|
endpoint: options.endpoint,
|
|
67
|
+
httpPort: parseInt(options.port),
|
|
60
68
|
retryAttempts: parseInt(options.retryAttempts),
|
|
61
69
|
retryDelay: parseInt(options.retryDelay),
|
|
70
|
+
useStdio,
|
|
62
71
|
tokenPreview: `${token.split(':')[0]}:${token.split(':')[1]}:${token.split(':')[2]?.substring(0, 8)}...`
|
|
63
72
|
}, 'Configuration loaded');
|
|
64
73
|
const client = new client_1.CelliumMCPClient({
|
|
65
74
|
token,
|
|
66
75
|
endpoint: options.endpoint,
|
|
76
|
+
httpPort: parseInt(options.port),
|
|
67
77
|
logger,
|
|
68
78
|
retryAttempts: parseInt(options.retryAttempts),
|
|
69
79
|
retryDelay: parseInt(options.retryDelay),
|
|
70
|
-
debugMode: !!options.debug
|
|
80
|
+
debugMode: !!options.debug,
|
|
81
|
+
useStdio
|
|
71
82
|
});
|
|
72
83
|
// Enhanced error handling for unhandled rejections
|
|
73
84
|
process.on('unhandledRejection', (reason, promise) => {
|
package/dist/client.d.ts
CHANGED
|
@@ -6,11 +6,14 @@ export interface CelliumMCPClientConfig {
|
|
|
6
6
|
retryAttempts?: number;
|
|
7
7
|
retryDelay?: number;
|
|
8
8
|
debugMode?: boolean;
|
|
9
|
+
httpPort?: number;
|
|
10
|
+
useStdio?: boolean;
|
|
9
11
|
}
|
|
10
12
|
export declare class CelliumMCPClient {
|
|
11
13
|
private config;
|
|
12
14
|
private localServer;
|
|
13
15
|
private transport?;
|
|
16
|
+
private httpServer?;
|
|
14
17
|
private isConnected;
|
|
15
18
|
private reconnectTimer?;
|
|
16
19
|
private keepAliveInterval?;
|
package/dist/client.js
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CelliumMCPClient = void 0;
|
|
4
4
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
5
|
+
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
5
6
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
7
|
+
const http_1 = require("http");
|
|
8
|
+
const crypto_1 = require("crypto");
|
|
6
9
|
const zod_1 = require("zod");
|
|
7
10
|
// Define schemas for MCP requests
|
|
8
11
|
const ToolsListSchema = zod_1.z.object({
|
|
@@ -45,6 +48,7 @@ class CelliumMCPClient {
|
|
|
45
48
|
config;
|
|
46
49
|
localServer;
|
|
47
50
|
transport;
|
|
51
|
+
httpServer;
|
|
48
52
|
isConnected = false;
|
|
49
53
|
reconnectTimer;
|
|
50
54
|
keepAliveInterval;
|
|
@@ -65,16 +69,19 @@ class CelliumMCPClient {
|
|
|
65
69
|
retryAttempts: 3,
|
|
66
70
|
retryDelay: 1000,
|
|
67
71
|
debugMode: false,
|
|
72
|
+
httpPort: 3001, // Default HTTP streaming port
|
|
73
|
+
useStdio: false,
|
|
68
74
|
...config
|
|
69
75
|
};
|
|
70
76
|
this.logDebug('Initializing CelliumMCPClient', {
|
|
71
77
|
endpoint: this.config.endpoint,
|
|
78
|
+
httpPort: this.config.httpPort,
|
|
72
79
|
debugMode: this.config.debugMode
|
|
73
80
|
});
|
|
74
|
-
// Local server that interfaces with AI assistants via
|
|
81
|
+
// Local server that interfaces with AI assistants via HTTP streaming
|
|
75
82
|
this.localServer = new mcp_js_1.McpServer({
|
|
76
83
|
name: 'cellium-mcp-client',
|
|
77
|
-
version: '
|
|
84
|
+
version: '2.0.0'
|
|
78
85
|
}, {
|
|
79
86
|
capabilities: {
|
|
80
87
|
tools: {},
|
|
@@ -216,7 +223,7 @@ class CelliumMCPClient {
|
|
|
216
223
|
},
|
|
217
224
|
serverInfo: {
|
|
218
225
|
name: 'cellium-mcp-client',
|
|
219
|
-
version: '
|
|
226
|
+
version: '2.0.0'
|
|
220
227
|
}
|
|
221
228
|
};
|
|
222
229
|
this.transportState.connected = true;
|
|
@@ -355,14 +362,14 @@ class CelliumMCPClient {
|
|
|
355
362
|
throw new Error('Cannot connect to remote Cellium server');
|
|
356
363
|
}
|
|
357
364
|
}
|
|
358
|
-
const
|
|
365
|
+
const httpStreamEndpoint = this.config.endpoint.replace('/sse', '/http-stream');
|
|
359
366
|
const requestBody = {
|
|
360
367
|
jsonrpc: '2.0',
|
|
361
368
|
id: requestId,
|
|
362
369
|
method,
|
|
363
370
|
params
|
|
364
371
|
};
|
|
365
|
-
this.logDebug(`Making HTTP request to ${
|
|
372
|
+
this.logDebug(`Making HTTP request to ${httpStreamEndpoint}`, {
|
|
366
373
|
requestId,
|
|
367
374
|
method,
|
|
368
375
|
bodySize: JSON.stringify(requestBody).length
|
|
@@ -370,12 +377,12 @@ class CelliumMCPClient {
|
|
|
370
377
|
let response;
|
|
371
378
|
let responseText;
|
|
372
379
|
try {
|
|
373
|
-
response = await fetch(
|
|
380
|
+
response = await fetch(httpStreamEndpoint, {
|
|
374
381
|
method: 'POST',
|
|
375
382
|
headers: {
|
|
376
383
|
'Authorization': `Bearer ${this.config.token}`,
|
|
377
384
|
'Content-Type': 'application/json',
|
|
378
|
-
'User-Agent': 'cellium-mcp-client/
|
|
385
|
+
'User-Agent': 'cellium-mcp-client/2.0.0'
|
|
379
386
|
},
|
|
380
387
|
body: JSON.stringify(requestBody)
|
|
381
388
|
});
|
|
@@ -434,7 +441,7 @@ class CelliumMCPClient {
|
|
|
434
441
|
requestId,
|
|
435
442
|
method,
|
|
436
443
|
duration,
|
|
437
|
-
endpoint:
|
|
444
|
+
endpoint: httpStreamEndpoint
|
|
438
445
|
}, 'HTTP request to remote server failed');
|
|
439
446
|
this.isConnected = false; // Mark as disconnected on error
|
|
440
447
|
throw error;
|
|
@@ -442,34 +449,90 @@ class CelliumMCPClient {
|
|
|
442
449
|
}
|
|
443
450
|
async connect() {
|
|
444
451
|
try {
|
|
452
|
+
const transportMode = this.config.useStdio ? 'stdio' : 'http-streaming';
|
|
445
453
|
this.config.logger.info({
|
|
446
454
|
endpoint: this.config.endpoint,
|
|
455
|
+
httpPort: this.config.httpPort,
|
|
447
456
|
debugMode: this.config.debugMode,
|
|
448
|
-
protocolVersion: this.mcpProtocolVersion
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
457
|
+
protocolVersion: this.mcpProtocolVersion,
|
|
458
|
+
transportMode
|
|
459
|
+
}, `Starting Cellium MCP Client with ${transportMode} transport`);
|
|
460
|
+
if (this.config.useStdio) {
|
|
461
|
+
// Use stdio transport for command-based MCP clients
|
|
462
|
+
this.logDebug('Initializing stdio transport');
|
|
463
|
+
this.transport = new stdio_js_1.StdioServerTransport();
|
|
464
|
+
// Add transport event monitoring if supported
|
|
465
|
+
if ('onclose' in this.transport) {
|
|
466
|
+
this.setupTransportEventListeners(this.transport);
|
|
467
|
+
}
|
|
468
|
+
await this.localServer.connect(this.transport);
|
|
469
|
+
this.transportState.connected = true;
|
|
470
|
+
this.transportState.lastActivity = Date.now();
|
|
471
|
+
this.config.logger.info('MCP stdio transport ready - communicating via stdin/stdout');
|
|
472
|
+
this.logDebug('Stdio transport connected successfully', {
|
|
473
|
+
transportType: 'stdio',
|
|
474
|
+
serverName: 'cellium-mcp-client',
|
|
475
|
+
serverVersion: '2.0.0'
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
// Use HTTP streaming transport (original implementation)
|
|
480
|
+
this.logDebug('Initializing HTTP streaming transport');
|
|
481
|
+
// Set up the HTTP streaming transport for local MCP server
|
|
482
|
+
this.transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
483
|
+
sessionIdGenerator: () => (0, crypto_1.randomUUID)(),
|
|
484
|
+
enableJsonResponse: false // Use SSE streaming for compatibility with codex
|
|
485
|
+
});
|
|
486
|
+
// Add transport event monitoring
|
|
487
|
+
this.setupTransportEventListeners(this.transport);
|
|
488
|
+
await this.localServer.connect(this.transport);
|
|
489
|
+
this.transportState.connected = true;
|
|
490
|
+
this.transportState.lastActivity = Date.now();
|
|
491
|
+
// Create HTTP server to handle requests
|
|
492
|
+
this.httpServer = (0, http_1.createServer)(async (req, res) => {
|
|
493
|
+
try {
|
|
494
|
+
await this.transport.handleRequest(req, res);
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
this.config.logger.error({ error }, 'Error handling HTTP request');
|
|
498
|
+
res.statusCode = 500;
|
|
499
|
+
res.end('Internal Server Error');
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
// Start HTTP server
|
|
503
|
+
await new Promise((resolve, reject) => {
|
|
504
|
+
this.httpServer.listen(this.config.httpPort, (err) => {
|
|
505
|
+
if (err) {
|
|
506
|
+
reject(err);
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
this.config.logger.info({
|
|
510
|
+
port: this.config.httpPort,
|
|
511
|
+
url: `http://localhost:${this.config.httpPort}`
|
|
512
|
+
}, 'HTTP streaming server started');
|
|
513
|
+
resolve();
|
|
514
|
+
}
|
|
515
|
+
});
|
|
470
516
|
});
|
|
471
|
-
|
|
472
|
-
|
|
517
|
+
this.config.logger.info(`MCP HTTP Streaming Server ready at http://localhost:${this.config.httpPort}`);
|
|
518
|
+
this.logDebug('HTTP streaming transport connected successfully', {
|
|
519
|
+
transportType: 'streamable-http',
|
|
520
|
+
port: this.config.httpPort,
|
|
521
|
+
serverName: 'cellium-mcp-client',
|
|
522
|
+
serverVersion: '2.0.0'
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
// For HTTP streaming mode, keep the process alive with a minimal interval
|
|
526
|
+
// For stdio mode, the process stays alive naturally via stdin/stdout communication
|
|
527
|
+
if (!this.config.useStdio) {
|
|
528
|
+
this.keepAliveInterval = setInterval(() => {
|
|
529
|
+
this.logDebug('Keep-alive tick', {
|
|
530
|
+
uptime: Date.now() - this.transportState.lastActivity,
|
|
531
|
+
activeRequests: this.activeRequests.size
|
|
532
|
+
});
|
|
533
|
+
}, 30000); // Check every 30 seconds
|
|
534
|
+
this.config.logger.debug('Keep-alive interval started for persistent HTTP MCP communication');
|
|
535
|
+
}
|
|
473
536
|
// Test connection to remote server and pre-fetch tools cache
|
|
474
537
|
await this.initializeRemoteConnection();
|
|
475
538
|
}
|
|
@@ -480,80 +543,79 @@ class CelliumMCPClient {
|
|
|
480
543
|
message: error.message,
|
|
481
544
|
stack: error.stack
|
|
482
545
|
} : error
|
|
483
|
-
}, 'Failed to start MCP server');
|
|
546
|
+
}, 'Failed to start MCP HTTP streaming server');
|
|
484
547
|
this.transportState.connected = false;
|
|
485
548
|
throw error;
|
|
486
549
|
}
|
|
487
550
|
}
|
|
488
551
|
setupTransportEventListeners(transport) {
|
|
489
552
|
this.logDebug('Setting up transport event listeners');
|
|
490
|
-
//
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
this.config.logger.error({ error }, 'Transport error event');
|
|
501
|
-
this.transportState.errorCount++;
|
|
502
|
-
});
|
|
503
|
-
transportAny.on('connect', () => {
|
|
504
|
-
this.config.logger.info('Transport connect event');
|
|
505
|
-
this.transportState.connected = true;
|
|
506
|
-
});
|
|
507
|
-
this.logDebug('Transport event listeners attached');
|
|
508
|
-
}
|
|
509
|
-
else {
|
|
510
|
-
this.logDebug('Transport does not support event listeners');
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
catch (error) {
|
|
514
|
-
this.logDebug('Could not attach transport event listeners', { error });
|
|
515
|
-
}
|
|
553
|
+
// Set up transport event handlers
|
|
554
|
+
transport.onclose = () => {
|
|
555
|
+
this.config.logger.warn('Transport close event detected');
|
|
556
|
+
this.transportState.connected = false;
|
|
557
|
+
};
|
|
558
|
+
transport.onerror = (error) => {
|
|
559
|
+
this.config.logger.error({ error }, 'Transport error event');
|
|
560
|
+
this.transportState.errorCount++;
|
|
561
|
+
};
|
|
562
|
+
this.logDebug('Transport event listeners attached');
|
|
516
563
|
}
|
|
517
564
|
async serve() {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
this.config.logger.
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
565
|
+
if (this.config.useStdio) {
|
|
566
|
+
// For stdio transport, the MCP communication happens automatically via stdin/stdout
|
|
567
|
+
// The server stays alive as long as the parent process keeps it alive
|
|
568
|
+
this.config.logger.debug({
|
|
569
|
+
transportConnected: this.transportState.connected,
|
|
570
|
+
transportMode: 'stdio',
|
|
571
|
+
serverName: 'cellium-mcp-client'
|
|
572
|
+
}, 'Stdio MCP server is now serving via stdin/stdout');
|
|
573
|
+
this.logDebug('Entering serve mode - stdio transport active');
|
|
574
|
+
// For stdio mode, we don't need to keep the process alive artificially
|
|
575
|
+
// The process will stay alive as long as stdin/stdout are connected
|
|
576
|
+
// No need to return a never-resolving promise
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
// For HTTP streaming mode, keep the process alive indefinitely
|
|
580
|
+
this.config.logger.debug({
|
|
581
|
+
transportConnected: this.transportState.connected,
|
|
582
|
+
httpPort: this.config.httpPort,
|
|
583
|
+
transportMode: 'http-streaming',
|
|
584
|
+
serverName: 'cellium-mcp-client'
|
|
585
|
+
}, 'HTTP streaming server is now serving and will stay alive for persistent MCP communication');
|
|
586
|
+
this.logDebug('Entering serve mode - HTTP server will stay alive');
|
|
587
|
+
// Set up graceful shutdown handlers
|
|
588
|
+
process.on('SIGINT', async () => {
|
|
589
|
+
this.config.logger.info('Received SIGINT, shutting down gracefully...');
|
|
590
|
+
await this.disconnect();
|
|
591
|
+
process.exit(0);
|
|
592
|
+
});
|
|
593
|
+
process.on('SIGTERM', async () => {
|
|
594
|
+
this.config.logger.info('Received SIGTERM, shutting down gracefully...');
|
|
595
|
+
await this.disconnect();
|
|
596
|
+
process.exit(0);
|
|
597
|
+
});
|
|
598
|
+
// Return a promise that never resolves to keep the process alive
|
|
599
|
+
// The process will only exit via SIGINT/SIGTERM signals
|
|
600
|
+
return new Promise(() => {
|
|
601
|
+
// Never resolve - keep process alive for HTTP streaming MCP communication
|
|
602
|
+
// Process will be terminated gracefully via signal handlers
|
|
603
|
+
});
|
|
604
|
+
}
|
|
543
605
|
}
|
|
544
606
|
async testConnection() {
|
|
545
607
|
const startTime = Date.now();
|
|
546
|
-
const
|
|
608
|
+
const httpStreamEndpoint = this.config.endpoint.replace('/sse', '/http-stream');
|
|
547
609
|
this.logDebug('Testing connection', {
|
|
548
|
-
endpoint:
|
|
610
|
+
endpoint: httpStreamEndpoint,
|
|
549
611
|
hasToken: !!this.config.token
|
|
550
612
|
});
|
|
551
|
-
const response = await fetch(
|
|
613
|
+
const response = await fetch(httpStreamEndpoint, {
|
|
552
614
|
method: 'POST',
|
|
553
615
|
headers: {
|
|
554
616
|
'Authorization': `Bearer ${this.config.token}`,
|
|
555
617
|
'Content-Type': 'application/json',
|
|
556
|
-
'User-Agent': 'cellium-mcp-client/
|
|
618
|
+
'User-Agent': 'cellium-mcp-client/2.0.0'
|
|
557
619
|
},
|
|
558
620
|
body: JSON.stringify({
|
|
559
621
|
jsonrpc: '2.0',
|
|
@@ -640,7 +702,7 @@ class CelliumMCPClient {
|
|
|
640
702
|
}
|
|
641
703
|
}
|
|
642
704
|
async disconnect() {
|
|
643
|
-
this.config.logger.info('Disconnecting from Cellium MCP Server');
|
|
705
|
+
this.config.logger.info('Disconnecting from Cellium MCP HTTP Streaming Server');
|
|
644
706
|
this.logDebug('Starting disconnect process', {
|
|
645
707
|
activeRequestCount: this.activeRequests.size,
|
|
646
708
|
transportConnected: this.transportState.connected
|
|
@@ -669,6 +731,15 @@ class CelliumMCPClient {
|
|
|
669
731
|
clearInterval(this.backgroundRefreshTimer);
|
|
670
732
|
this.backgroundRefreshTimer = undefined;
|
|
671
733
|
}
|
|
734
|
+
// Close HTTP server
|
|
735
|
+
if (this.httpServer) {
|
|
736
|
+
await new Promise((resolve) => {
|
|
737
|
+
this.httpServer.close(() => {
|
|
738
|
+
this.config.logger.info('HTTP streaming server closed');
|
|
739
|
+
resolve();
|
|
740
|
+
});
|
|
741
|
+
});
|
|
742
|
+
}
|
|
672
743
|
try {
|
|
673
744
|
await this.localServer.close();
|
|
674
745
|
this.logDebug('Local MCP server closed successfully');
|