cellium-mcp-client 1.0.3 → 1.1.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.
- package/README.md +18 -27
- package/dist/cli.js +1 -1
- package/dist/client.d.ts +1 -3
- package/dist/client.js +60 -55
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @cellium/mcp-client
|
|
2
2
|
|
|
3
|
-
A Model Context Protocol (MCP)
|
|
3
|
+
A Model Context Protocol (MCP) server that proxies requests to remote Cellium processor servers.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **
|
|
7
|
+
- **HTTP Proxy**: Proxies MCP requests to remote Cellium servers via HTTP
|
|
8
8
|
- **Authentication**: Token-based authentication with format `user:username:hash`
|
|
9
9
|
- **Robust Connection Management**: Automatic reconnection with configurable retry logic
|
|
10
10
|
- **MCP Protocol Compliance**: Uses official `@modelcontextprotocol/sdk`
|
|
@@ -38,7 +38,7 @@ cellium-mcp-client --token "user:your-username:your-hash"
|
|
|
38
38
|
# With custom endpoint and verbose logging
|
|
39
39
|
cellium-mcp-client \
|
|
40
40
|
--token "user:your-username:your-hash" \
|
|
41
|
-
--endpoint "https://mcp.cellium.dev/
|
|
41
|
+
--endpoint "https://mcp.cellium.dev/mcp" \
|
|
42
42
|
--verbose \
|
|
43
43
|
--retry-attempts 5 \
|
|
44
44
|
--retry-delay 2000
|
|
@@ -66,7 +66,7 @@ const logger = pino();
|
|
|
66
66
|
|
|
67
67
|
const client = new CelliumMCPClient({
|
|
68
68
|
token: 'user:your-username:your-hash',
|
|
69
|
-
endpoint: 'https://mcp.cellium.dev/
|
|
69
|
+
endpoint: 'https://mcp.cellium.dev/mcp',
|
|
70
70
|
logger,
|
|
71
71
|
retryAttempts: 3,
|
|
72
72
|
retryDelay: 1000
|
|
@@ -87,7 +87,7 @@ process.on('SIGINT', async () => {
|
|
|
87
87
|
| Option | Environment Variable | Description | Default |
|
|
88
88
|
|--------|---------------------|-------------|---------|
|
|
89
89
|
| `--token` | `CELLIUM_MCP_TOKEN` | Authentication token (required) | - |
|
|
90
|
-
| `--endpoint` | - | Server endpoint URL | `https://mcp.cellium.dev/
|
|
90
|
+
| `--endpoint` | - | Server endpoint URL | `https://mcp.cellium.dev/mcp` |
|
|
91
91
|
| `--verbose` | - | Enable verbose logging | `false` |
|
|
92
92
|
| `--retry-attempts` | - | Number of retry attempts | `3` |
|
|
93
93
|
| `--retry-delay` | - | Delay between retries (ms) | `1000` |
|
|
@@ -106,29 +106,20 @@ Example: `user:john-doe:a1b2c3d4e5f6...`
|
|
|
106
106
|
## Architecture
|
|
107
107
|
|
|
108
108
|
```
|
|
109
|
-
┌─────────────────┐
|
|
110
|
-
│ │◄──────────►│ │
|
|
111
|
-
│
|
|
112
|
-
│ (
|
|
113
|
-
│ │ │ │
|
|
114
|
-
└─────────────────┘ └─────────────────┘
|
|
115
|
-
▲ │
|
|
116
|
-
│ stdio │
|
|
117
|
-
│ MCP Protocol │
|
|
118
|
-
▼ │
|
|
119
|
-
┌─────────────────┐ │
|
|
120
|
-
│ │ │
|
|
121
|
-
│ Code Editor / │ │
|
|
122
|
-
│ AI Assistant │ │
|
|
123
|
-
│ (Cody, etc.) │ │
|
|
124
|
-
└─────────────────┘ │
|
|
109
|
+
┌─────────────────┐ stdio ┌─────────────────┐ HTTPS ┌─────────────────┐
|
|
110
|
+
│ │◄──────────►│ │◄──────────►│ │
|
|
111
|
+
│ Code Editor / │ MCP │ cellium-mcp- │ HTTP │ Cellium Server │
|
|
112
|
+
│ AI Assistant │ Protocol │ client (Proxy) │ Requests │ (Remote) │
|
|
113
|
+
│ (Cody, etc.) │ │ │ │ │
|
|
114
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
125
115
|
```
|
|
126
116
|
|
|
127
|
-
The client acts as
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
131
|
-
-
|
|
117
|
+
The client acts as an **MCP server proxy** between local MCP clients (like Cody) and the remote Cellium processor server, handling:
|
|
118
|
+
- **MCP Protocol**: Full stdio-based MCP server implementation
|
|
119
|
+
- **Authentication**: Token-based authentication with remote server
|
|
120
|
+
- **Request Proxying**: Converts MCP requests to HTTP requests to remote server
|
|
121
|
+
- **Error Handling**: Graceful handling of connection failures
|
|
122
|
+
- **Connection Management**: Automatic retry and reconnection logic
|
|
132
123
|
|
|
133
124
|
## Development
|
|
134
125
|
|
|
@@ -153,7 +144,7 @@ npm run dev
|
|
|
153
144
|
### Connection Issues
|
|
154
145
|
|
|
155
146
|
1. **Invalid token format**: Ensure your token follows the `user:username:hash` format
|
|
156
|
-
2. **Network connectivity**: Check if `https://mcp.cellium.dev/
|
|
147
|
+
2. **Network connectivity**: Check if `https://mcp.cellium.dev/mcp` is accessible
|
|
157
148
|
3. **Authentication failed**: Verify your token is valid and not expired
|
|
158
149
|
|
|
159
150
|
### Verbose Logging
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ 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('1.
|
|
14
|
+
.version('1.1.1')
|
|
15
15
|
.option('-t, --token <token>', 'Authentication token (format: user:username:hash)')
|
|
16
16
|
.option('-e, --endpoint <url>', 'Server endpoint URL', 'http://localhost:3000/mcp')
|
|
17
17
|
.option('-v, --verbose', 'Enable verbose logging')
|
package/dist/client.d.ts
CHANGED
|
@@ -11,14 +11,12 @@ export declare class CelliumMCPClient {
|
|
|
11
11
|
private localServer;
|
|
12
12
|
private isConnected;
|
|
13
13
|
private reconnectTimer?;
|
|
14
|
-
private currentRetryCount;
|
|
15
14
|
constructor(config: CelliumMCPClientConfig);
|
|
16
15
|
private setupServer;
|
|
17
16
|
private makeHttpRequest;
|
|
18
17
|
connect(): Promise<void>;
|
|
18
|
+
private testConnectionInBackground;
|
|
19
19
|
serve(): Promise<void>;
|
|
20
20
|
private testConnection;
|
|
21
|
-
private handleConnectionError;
|
|
22
|
-
private reconnect;
|
|
23
21
|
disconnect(): Promise<void>;
|
|
24
22
|
}
|
package/dist/client.js
CHANGED
|
@@ -30,12 +30,22 @@ const PingSchema = zod_1.z.object({
|
|
|
30
30
|
method: zod_1.z.literal('ping'),
|
|
31
31
|
params: zod_1.z.object({}).optional()
|
|
32
32
|
});
|
|
33
|
+
const InitializeSchema = zod_1.z.object({
|
|
34
|
+
method: zod_1.z.literal('initialize'),
|
|
35
|
+
params: zod_1.z.object({
|
|
36
|
+
protocolVersion: zod_1.z.string(),
|
|
37
|
+
capabilities: zod_1.z.object({}).passthrough()
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
const InitializedNotificationSchema = zod_1.z.object({
|
|
41
|
+
method: zod_1.z.literal('notifications/initialized'),
|
|
42
|
+
params: zod_1.z.object({}).optional()
|
|
43
|
+
});
|
|
33
44
|
class CelliumMCPClient {
|
|
34
45
|
config;
|
|
35
46
|
localServer;
|
|
36
47
|
isConnected = false;
|
|
37
48
|
reconnectTimer;
|
|
38
|
-
currentRetryCount = 0;
|
|
39
49
|
constructor(config) {
|
|
40
50
|
this.config = {
|
|
41
51
|
retryAttempts: 3,
|
|
@@ -55,57 +65,67 @@ class CelliumMCPClient {
|
|
|
55
65
|
this.setupServer();
|
|
56
66
|
}
|
|
57
67
|
setupServer() {
|
|
58
|
-
//
|
|
59
|
-
this.localServer.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
// Handle MCP initialization
|
|
69
|
+
this.localServer.server.setRequestHandler(InitializeSchema, async (_request) => {
|
|
70
|
+
this.config.logger.debug('Received initialize request');
|
|
71
|
+
return {
|
|
72
|
+
protocolVersion: '2024-11-05',
|
|
73
|
+
capabilities: {
|
|
74
|
+
tools: {},
|
|
75
|
+
resources: {}
|
|
76
|
+
},
|
|
77
|
+
serverInfo: {
|
|
78
|
+
name: 'cellium-mcp-client',
|
|
79
|
+
version: '1.1.1'
|
|
80
|
+
}
|
|
81
|
+
};
|
|
64
82
|
});
|
|
65
83
|
// Override the underlying server's tool request handlers to proxy to remote
|
|
66
84
|
this.localServer.server.setRequestHandler(ToolsListSchema, async () => {
|
|
67
85
|
this.config.logger.debug('Proxying tools/list to remote server');
|
|
68
|
-
if (!this.isConnected) {
|
|
69
|
-
throw new Error('Not connected to remote server');
|
|
70
|
-
}
|
|
71
86
|
const result = await this.makeHttpRequest('tools/list', {});
|
|
72
87
|
return result;
|
|
73
88
|
});
|
|
74
89
|
this.localServer.server.setRequestHandler(ToolsCallSchema, async (request) => {
|
|
75
90
|
this.config.logger.debug({ toolName: request.params?.name }, 'Proxying tool call to remote server');
|
|
76
|
-
if (!this.isConnected) {
|
|
77
|
-
throw new Error('Not connected to remote server');
|
|
78
|
-
}
|
|
79
91
|
const result = await this.makeHttpRequest('tools/call', request.params);
|
|
80
92
|
return result;
|
|
81
93
|
});
|
|
82
94
|
// Handle resources as well
|
|
83
95
|
this.localServer.server.setRequestHandler(ResourcesListSchema, async () => {
|
|
84
96
|
this.config.logger.debug('Proxying resources/list to remote server');
|
|
85
|
-
if (!this.isConnected) {
|
|
86
|
-
throw new Error('Not connected to remote server');
|
|
87
|
-
}
|
|
88
97
|
const result = await this.makeHttpRequest('resources/list', {});
|
|
89
98
|
return result;
|
|
90
99
|
});
|
|
91
100
|
this.localServer.server.setRequestHandler(ResourcesReadSchema, async (request) => {
|
|
92
101
|
this.config.logger.debug({ uri: request.params?.uri }, 'Proxying resources/read to remote server');
|
|
93
|
-
if (!this.isConnected) {
|
|
94
|
-
throw new Error('Not connected to remote server');
|
|
95
|
-
}
|
|
96
102
|
const result = await this.makeHttpRequest('resources/read', request.params);
|
|
97
103
|
return result;
|
|
98
104
|
});
|
|
99
105
|
// Handle ping
|
|
100
106
|
this.localServer.server.setRequestHandler(PingSchema, async () => {
|
|
101
|
-
if (!this.isConnected) {
|
|
102
|
-
throw new Error('Not connected to remote server');
|
|
103
|
-
}
|
|
104
107
|
const result = await this.makeHttpRequest('ping', {});
|
|
105
108
|
return result;
|
|
106
109
|
});
|
|
110
|
+
// Handle other common MCP methods
|
|
111
|
+
this.localServer.server.setNotificationHandler(InitializedNotificationSchema, async () => {
|
|
112
|
+
this.config.logger.debug('Received initialized notification');
|
|
113
|
+
// No response needed for notifications
|
|
114
|
+
});
|
|
107
115
|
}
|
|
108
116
|
async makeHttpRequest(method, params) {
|
|
117
|
+
// If not connected, try to connect first
|
|
118
|
+
if (!this.isConnected) {
|
|
119
|
+
try {
|
|
120
|
+
await this.testConnection();
|
|
121
|
+
this.isConnected = true;
|
|
122
|
+
this.config.logger.info('Connected to remote Cellium server');
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
this.config.logger.error({ error }, 'Failed to connect to remote server');
|
|
126
|
+
throw new Error('Cannot connect to remote Cellium server');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
109
129
|
const mcpEndpoint = this.config.endpoint.replace('/sse', '/mcp');
|
|
110
130
|
const requestBody = {
|
|
111
131
|
jsonrpc: '2.0',
|
|
@@ -134,31 +154,38 @@ class CelliumMCPClient {
|
|
|
134
154
|
}
|
|
135
155
|
catch (error) {
|
|
136
156
|
this.config.logger.error({ error, method }, 'HTTP request to remote server failed');
|
|
157
|
+
this.isConnected = false; // Mark as disconnected on error
|
|
137
158
|
throw error;
|
|
138
159
|
}
|
|
139
160
|
}
|
|
140
161
|
async connect() {
|
|
141
162
|
try {
|
|
142
|
-
this.config.logger.info({ endpoint: this.config.endpoint }, '
|
|
143
|
-
//
|
|
163
|
+
this.config.logger.info({ endpoint: this.config.endpoint }, 'Starting Cellium MCP Server');
|
|
164
|
+
// Set up the stdio transport for local MCP server immediately
|
|
165
|
+
const stdioTransport = new stdio_js_1.StdioServerTransport();
|
|
166
|
+
await this.localServer.connect(stdioTransport);
|
|
167
|
+
this.config.logger.info('MCP Server connected and ready');
|
|
168
|
+
// Test connection to remote server in background, but don't block startup
|
|
169
|
+
this.testConnectionInBackground();
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
this.config.logger.error({ error }, 'Failed to start MCP server');
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async testConnectionInBackground() {
|
|
177
|
+
try {
|
|
144
178
|
await this.testConnection();
|
|
145
179
|
this.isConnected = true;
|
|
146
|
-
this.
|
|
180
|
+
this.config.logger.info('Connected to remote Cellium server');
|
|
147
181
|
if (this.reconnectTimer) {
|
|
148
182
|
clearTimeout(this.reconnectTimer);
|
|
149
183
|
this.reconnectTimer = undefined;
|
|
150
184
|
}
|
|
151
|
-
this.config.logger.info('Connected to remote Cellium server');
|
|
152
|
-
// Set up the stdio transport for local MCP server
|
|
153
|
-
const stdioTransport = new stdio_js_1.StdioServerTransport();
|
|
154
|
-
await this.localServer.connect(stdioTransport);
|
|
155
|
-
this.config.logger.info('MCP Client connected and ready');
|
|
156
185
|
}
|
|
157
186
|
catch (error) {
|
|
158
|
-
this.config.logger.
|
|
187
|
+
this.config.logger.warn({ error }, 'Failed to connect to remote server, will retry on first request');
|
|
159
188
|
this.isConnected = false;
|
|
160
|
-
this.handleConnectionError();
|
|
161
|
-
throw error;
|
|
162
189
|
}
|
|
163
190
|
}
|
|
164
191
|
async serve() {
|
|
@@ -192,28 +219,6 @@ class CelliumMCPClient {
|
|
|
192
219
|
}
|
|
193
220
|
this.config.logger.debug('Connection test successful');
|
|
194
221
|
}
|
|
195
|
-
handleConnectionError() {
|
|
196
|
-
if (this.currentRetryCount < this.config.retryAttempts) {
|
|
197
|
-
this.currentRetryCount++;
|
|
198
|
-
this.config.logger.warn(`Connection failed, retrying in ${this.config.retryDelay}ms (attempt ${this.currentRetryCount}/${this.config.retryAttempts})`);
|
|
199
|
-
this.reconnectTimer = setTimeout(() => {
|
|
200
|
-
this.reconnect();
|
|
201
|
-
}, this.config.retryDelay);
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
this.config.logger.error('Max retry attempts reached, giving up');
|
|
205
|
-
process.exit(1);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
async reconnect() {
|
|
209
|
-
try {
|
|
210
|
-
await this.connect();
|
|
211
|
-
}
|
|
212
|
-
catch (error) {
|
|
213
|
-
this.config.logger.error({ error }, 'Reconnection failed');
|
|
214
|
-
this.handleConnectionError();
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
222
|
async disconnect() {
|
|
218
223
|
this.config.logger.info('Disconnecting from Cellium MCP Server');
|
|
219
224
|
this.isConnected = false;
|