servicenow-mcp-server 2.1.4 โ 2.1.6
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/.claude/settings.local.json +3 -1
- package/README.md +11 -3
- package/docker-compose.yml +3 -0
- package/docs/SSE_DOCKER_SETUP.md +514 -0
- package/docs/SSE_FIX_SUMMARY.md +277 -0
- package/package.json +1 -1
- package/src/server.js +50 -9
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<img src="assets/logo.svg" alt="ServiceNow MCP Server" width="200"/>
|
|
3
3
|
|
|
4
|
-
# ServiceNow MCP Server v2.1
|
|
4
|
+
# ServiceNow MCP Server v2.1.5
|
|
5
5
|
|
|
6
6
|
**Multi-Instance Intelligent Architecture**
|
|
7
7
|
|
|
@@ -30,9 +30,17 @@
|
|
|
30
30
|
- **๐ฃ๏ธ Natural Language Search**: Query ServiceNow using plain English queries
|
|
31
31
|
- **๐ MCP Resources**: 8 read-only resource URIs for quick lookups and documentation
|
|
32
32
|
|
|
33
|
-
## ๐ What's New in v2.1
|
|
33
|
+
## ๐ What's New in v2.1.5
|
|
34
34
|
|
|
35
|
-
**
|
|
35
|
+
**November 2025 Release**
|
|
36
|
+
|
|
37
|
+
- ๐ **SSE Keepalive Fix**: Automatic heartbeat mechanism prevents Docker SSE connection drops (15s default, configurable)
|
|
38
|
+
- ๐ **Security Patches**: Fixed high-severity glob vulnerability (CVE-2025-64756)
|
|
39
|
+
- ๐ณ **Production Ready**: Optimized SSE configuration for Docker, Kubernetes, and proxy deployments
|
|
40
|
+
- ๐ **Complete Documentation**: Comprehensive SSE setup guide with nginx/Traefik/HAProxy configurations
|
|
41
|
+
- ๐ **Connection Monitoring**: Automatic cleanup and lifecycle logging for debugging
|
|
42
|
+
|
|
43
|
+
**October 2025 Release (v2.1)**
|
|
36
44
|
|
|
37
45
|
- ๐จ **Local Script Development**: Sync scripts with Git, watch mode for continuous development, full version control integration
|
|
38
46
|
- ๐ฃ๏ธ **Natural Language Search**: Query ServiceNow using plain English (15+ supported patterns)
|
package/docker-compose.yml
CHANGED
|
@@ -18,6 +18,9 @@ services:
|
|
|
18
18
|
|
|
19
19
|
# Multi-instance mode (specify instance name)
|
|
20
20
|
- SERVICENOW_INSTANCE=${SERVICENOW_INSTANCE}
|
|
21
|
+
|
|
22
|
+
# SSE Configuration
|
|
23
|
+
- SSE_KEEPALIVE_INTERVAL=${SSE_KEEPALIVE_INTERVAL:-15000}
|
|
21
24
|
volumes:
|
|
22
25
|
# Mount config file for multi-instance support
|
|
23
26
|
- ./config/servicenow-instances.json:/app/config/servicenow-instances.json:ro
|
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
# SSE (Server-Sent Events) Setup Guide for Docker
|
|
2
|
+
|
|
3
|
+
**Problem:** SSE connections dropping in Docker environments
|
|
4
|
+
**Solution:** Keepalive heartbeat + proper configuration
|
|
5
|
+
**Version:** 2.1.4+
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ๐ Root Cause
|
|
10
|
+
|
|
11
|
+
SSE connections drop in Docker due to:
|
|
12
|
+
|
|
13
|
+
1. **Proxy/Load Balancer Timeouts** - nginx, HAProxy, etc. timeout idle connections
|
|
14
|
+
2. **Docker Network Timeouts** - Docker networking kills idle connections
|
|
15
|
+
3. **Missing Keepalive** - No heartbeat to keep connection alive
|
|
16
|
+
4. **Buffering Issues** - Proxies buffer SSE events, breaking the stream
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## โ
Fixed in v2.1.4
|
|
21
|
+
|
|
22
|
+
The ServiceNow MCP Server now includes:
|
|
23
|
+
|
|
24
|
+
- โ
**Automatic keepalive heartbeat** (every 15 seconds by default)
|
|
25
|
+
- โ
**Disabled timeouts** for SSE endpoint (infinite connection)
|
|
26
|
+
- โ
**Proxy-friendly headers** (`X-Accel-Buffering: no`)
|
|
27
|
+
- โ
**Connection monitoring** with automatic cleanup
|
|
28
|
+
- โ
**Configurable keepalive interval** via environment variable
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## ๐ Quick Start (Docker)
|
|
33
|
+
|
|
34
|
+
### 1. Basic Docker Run
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
docker run -d \
|
|
38
|
+
--name servicenow-mcp \
|
|
39
|
+
-p 3000:3000 \
|
|
40
|
+
-e SERVICENOW_INSTANCE_URL=https://dev12345.service-now.com \
|
|
41
|
+
-e SERVICENOW_USERNAME=admin \
|
|
42
|
+
-e SERVICENOW_PASSWORD=password \
|
|
43
|
+
-e SSE_KEEPALIVE_INTERVAL=15000 \
|
|
44
|
+
nczitzer/mcp-servicenow-nodejs:latest
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Docker Compose
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
version: '3.8'
|
|
51
|
+
|
|
52
|
+
services:
|
|
53
|
+
servicenow-mcp-server:
|
|
54
|
+
image: nczitzer/mcp-servicenow-nodejs:latest
|
|
55
|
+
container_name: servicenow-mcp-server
|
|
56
|
+
ports:
|
|
57
|
+
- "3000:3000"
|
|
58
|
+
environment:
|
|
59
|
+
- SERVICENOW_INSTANCE_URL=${SERVICENOW_INSTANCE_URL}
|
|
60
|
+
- SERVICENOW_USERNAME=${SERVICENOW_USERNAME}
|
|
61
|
+
- SERVICENOW_PASSWORD=${SERVICENOW_PASSWORD}
|
|
62
|
+
|
|
63
|
+
# SSE Configuration (optional)
|
|
64
|
+
- SSE_KEEPALIVE_INTERVAL=15000 # 15 seconds (default)
|
|
65
|
+
|
|
66
|
+
volumes:
|
|
67
|
+
- ./config/servicenow-instances.json:/app/config/servicenow-instances.json:ro
|
|
68
|
+
restart: unless-stopped
|
|
69
|
+
healthcheck:
|
|
70
|
+
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
|
|
71
|
+
interval: 30s
|
|
72
|
+
timeout: 10s
|
|
73
|
+
retries: 3
|
|
74
|
+
start_period: 10s
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. Test the Connection
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Terminal 1: Start server
|
|
81
|
+
docker-compose up
|
|
82
|
+
|
|
83
|
+
# Terminal 2: Test SSE connection
|
|
84
|
+
curl -N http://localhost:3000/mcp
|
|
85
|
+
|
|
86
|
+
# You should see keepalive comments every 15 seconds:
|
|
87
|
+
# : keepalive
|
|
88
|
+
#
|
|
89
|
+
# : keepalive
|
|
90
|
+
#
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## โ๏ธ Configuration
|
|
96
|
+
|
|
97
|
+
### Environment Variables
|
|
98
|
+
|
|
99
|
+
| Variable | Description | Default | Example |
|
|
100
|
+
|----------|-------------|---------|---------|
|
|
101
|
+
| `SSE_KEEPALIVE_INTERVAL` | Keepalive interval in milliseconds | `15000` | `10000` |
|
|
102
|
+
| `PORT` | HTTP server port | `3000` | `8080` |
|
|
103
|
+
| `DEBUG` | Enable debug logging | `false` | `true` |
|
|
104
|
+
|
|
105
|
+
### Recommended Settings
|
|
106
|
+
|
|
107
|
+
**Default (15 seconds)** - Works for most environments:
|
|
108
|
+
```bash
|
|
109
|
+
SSE_KEEPALIVE_INTERVAL=15000
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Behind aggressive proxies (10 seconds):**
|
|
113
|
+
```bash
|
|
114
|
+
SSE_KEEPALIVE_INTERVAL=10000
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Low-latency networks (30 seconds):**
|
|
118
|
+
```bash
|
|
119
|
+
SSE_KEEPALIVE_INTERVAL=30000
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**โ ๏ธ DO NOT set below 5 seconds** - Creates unnecessary network traffic
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## ๐ง Reverse Proxy Configuration
|
|
127
|
+
|
|
128
|
+
### nginx
|
|
129
|
+
|
|
130
|
+
**Problem:** nginx buffers responses by default, breaking SSE
|
|
131
|
+
|
|
132
|
+
**Solution:** Disable buffering for `/mcp` endpoint
|
|
133
|
+
|
|
134
|
+
```nginx
|
|
135
|
+
server {
|
|
136
|
+
listen 80;
|
|
137
|
+
server_name your-domain.com;
|
|
138
|
+
|
|
139
|
+
location /mcp {
|
|
140
|
+
proxy_pass http://servicenow-mcp:3000;
|
|
141
|
+
|
|
142
|
+
# SSE-specific settings
|
|
143
|
+
proxy_buffering off; # Disable buffering
|
|
144
|
+
proxy_cache off; # Disable caching
|
|
145
|
+
proxy_read_timeout 86400s; # 24 hours
|
|
146
|
+
proxy_connect_timeout 60s;
|
|
147
|
+
|
|
148
|
+
# Required headers
|
|
149
|
+
proxy_set_header Connection '';
|
|
150
|
+
proxy_set_header Cache-Control 'no-cache';
|
|
151
|
+
proxy_set_header X-Accel-Buffering 'no';
|
|
152
|
+
|
|
153
|
+
# Standard proxy headers
|
|
154
|
+
proxy_set_header Host $host;
|
|
155
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
156
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
157
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
158
|
+
|
|
159
|
+
# HTTP/1.1 for SSE
|
|
160
|
+
proxy_http_version 1.1;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# Health check endpoint (buffered is OK)
|
|
164
|
+
location /health {
|
|
165
|
+
proxy_pass http://servicenow-mcp:3000;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Traefik
|
|
171
|
+
|
|
172
|
+
```yaml
|
|
173
|
+
http:
|
|
174
|
+
routers:
|
|
175
|
+
servicenow-mcp:
|
|
176
|
+
rule: "Host(`your-domain.com`)"
|
|
177
|
+
service: servicenow-mcp
|
|
178
|
+
|
|
179
|
+
services:
|
|
180
|
+
servicenow-mcp:
|
|
181
|
+
loadBalancer:
|
|
182
|
+
servers:
|
|
183
|
+
- url: "http://servicenow-mcp:3000"
|
|
184
|
+
# Disable buffering
|
|
185
|
+
responseForwarding:
|
|
186
|
+
flushInterval: "1s"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### HAProxy
|
|
190
|
+
|
|
191
|
+
```conf
|
|
192
|
+
frontend http_front
|
|
193
|
+
bind *:80
|
|
194
|
+
default_backend servicenow_mcp
|
|
195
|
+
|
|
196
|
+
backend servicenow_mcp
|
|
197
|
+
server mcp1 servicenow-mcp:3000 check
|
|
198
|
+
|
|
199
|
+
# SSE configuration
|
|
200
|
+
timeout server 86400s
|
|
201
|
+
timeout tunnel 86400s
|
|
202
|
+
option http-server-close
|
|
203
|
+
option forwardfor
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## ๐ Troubleshooting
|
|
209
|
+
|
|
210
|
+
### Connection Still Dropping?
|
|
211
|
+
|
|
212
|
+
**1. Check keepalive logs:**
|
|
213
|
+
```bash
|
|
214
|
+
docker logs servicenow-mcp-server
|
|
215
|
+
|
|
216
|
+
# Look for:
|
|
217
|
+
# ๐ New session established: <session-id>
|
|
218
|
+
# ๐ SSE keepalive interval: 15000ms
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**2. Monitor connection:**
|
|
222
|
+
```bash
|
|
223
|
+
# Watch SSE stream
|
|
224
|
+
curl -N http://localhost:3000/mcp
|
|
225
|
+
|
|
226
|
+
# You should see keepalive comments every 15 seconds
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**3. Test with shorter interval:**
|
|
230
|
+
```bash
|
|
231
|
+
docker run -e SSE_KEEPALIVE_INTERVAL=5000 ...
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**4. Check proxy timeouts:**
|
|
235
|
+
```bash
|
|
236
|
+
# nginx
|
|
237
|
+
grep timeout /etc/nginx/nginx.conf
|
|
238
|
+
|
|
239
|
+
# Look for:
|
|
240
|
+
# proxy_read_timeout 60s; # INCREASE THIS
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Common Issues
|
|
244
|
+
|
|
245
|
+
**Problem:** Connection drops after exactly 60 seconds
|
|
246
|
+
**Cause:** nginx default `proxy_read_timeout 60s`
|
|
247
|
+
**Solution:** Set `proxy_read_timeout 86400s;` in nginx config
|
|
248
|
+
|
|
249
|
+
**Problem:** Connection drops after 2 minutes
|
|
250
|
+
**Cause:** Docker network idle timeout
|
|
251
|
+
**Solution:** Reduce `SSE_KEEPALIVE_INTERVAL` to 10 seconds
|
|
252
|
+
|
|
253
|
+
**Problem:** No data received, connection hangs
|
|
254
|
+
**Cause:** Proxy buffering enabled
|
|
255
|
+
**Solution:** Add `proxy_buffering off;` to nginx config
|
|
256
|
+
|
|
257
|
+
**Problem:** Works locally, fails in production
|
|
258
|
+
**Cause:** Load balancer timeout
|
|
259
|
+
**Solution:** Configure load balancer timeout > keepalive interval
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## ๐ Connection Monitoring
|
|
264
|
+
|
|
265
|
+
### Server-Side Logs
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
# Watch connection events
|
|
269
|
+
docker logs -f servicenow-mcp-server
|
|
270
|
+
|
|
271
|
+
# Output:
|
|
272
|
+
๐ New session established: abc123
|
|
273
|
+
๐ SSE keepalive interval: 15000ms
|
|
274
|
+
๐ Client disconnected: abc123
|
|
275
|
+
๐งน Cleaned up session abc123
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Client-Side Testing
|
|
279
|
+
|
|
280
|
+
**Test script (test-sse.js):**
|
|
281
|
+
```javascript
|
|
282
|
+
const EventSource = require('eventsource');
|
|
283
|
+
|
|
284
|
+
const url = 'http://localhost:3000/mcp';
|
|
285
|
+
const es = new EventSource(url);
|
|
286
|
+
|
|
287
|
+
let keepaliveCount = 0;
|
|
288
|
+
|
|
289
|
+
es.onopen = () => {
|
|
290
|
+
console.log('โ
Connected to SSE');
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
es.onerror = (error) => {
|
|
294
|
+
console.error('โ SSE Error:', error);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
es.onmessage = (event) => {
|
|
298
|
+
if (event.data === 'keepalive') {
|
|
299
|
+
keepaliveCount++;
|
|
300
|
+
console.log(`๐ Keepalive received (${keepaliveCount})`);
|
|
301
|
+
} else {
|
|
302
|
+
console.log('๐จ Message:', event.data);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// Exit after 2 minutes
|
|
307
|
+
setTimeout(() => {
|
|
308
|
+
console.log(`\n๐ Total keepalives: ${keepaliveCount}`);
|
|
309
|
+
console.log('โ
Connection stable!');
|
|
310
|
+
es.close();
|
|
311
|
+
process.exit(0);
|
|
312
|
+
}, 120000);
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Run test:**
|
|
316
|
+
```bash
|
|
317
|
+
npm install eventsource
|
|
318
|
+
node test-sse.js
|
|
319
|
+
|
|
320
|
+
# Expected output:
|
|
321
|
+
# โ
Connected to SSE
|
|
322
|
+
# ๐ Keepalive received (1)
|
|
323
|
+
# ๐ Keepalive received (2)
|
|
324
|
+
# ...
|
|
325
|
+
# ๐ Total keepalives: 8
|
|
326
|
+
# โ
Connection stable!
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## ๐ Advanced Configuration
|
|
332
|
+
|
|
333
|
+
### Multiple Instances Behind Load Balancer
|
|
334
|
+
|
|
335
|
+
```yaml
|
|
336
|
+
version: '3.8'
|
|
337
|
+
|
|
338
|
+
services:
|
|
339
|
+
servicenow-mcp-1:
|
|
340
|
+
image: nczitzer/mcp-servicenow-nodejs:latest
|
|
341
|
+
environment:
|
|
342
|
+
- SSE_KEEPALIVE_INTERVAL=10000
|
|
343
|
+
|
|
344
|
+
servicenow-mcp-2:
|
|
345
|
+
image: nczitzer/mcp-servicenow-nodejs:latest
|
|
346
|
+
environment:
|
|
347
|
+
- SSE_KEEPALIVE_INTERVAL=10000
|
|
348
|
+
|
|
349
|
+
nginx:
|
|
350
|
+
image: nginx:alpine
|
|
351
|
+
ports:
|
|
352
|
+
- "80:80"
|
|
353
|
+
volumes:
|
|
354
|
+
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
|
355
|
+
depends_on:
|
|
356
|
+
- servicenow-mcp-1
|
|
357
|
+
- servicenow-mcp-2
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**nginx.conf for load balancing:**
|
|
361
|
+
```nginx
|
|
362
|
+
upstream servicenow_mcp {
|
|
363
|
+
# Sticky sessions for SSE (hash by IP)
|
|
364
|
+
ip_hash;
|
|
365
|
+
|
|
366
|
+
server servicenow-mcp-1:3000;
|
|
367
|
+
server servicenow-mcp-2:3000;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
server {
|
|
371
|
+
listen 80;
|
|
372
|
+
|
|
373
|
+
location /mcp {
|
|
374
|
+
proxy_pass http://servicenow_mcp;
|
|
375
|
+
proxy_buffering off;
|
|
376
|
+
proxy_read_timeout 86400s;
|
|
377
|
+
proxy_http_version 1.1;
|
|
378
|
+
proxy_set_header Connection '';
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Kubernetes Deployment
|
|
384
|
+
|
|
385
|
+
```yaml
|
|
386
|
+
apiVersion: apps/v1
|
|
387
|
+
kind: Deployment
|
|
388
|
+
metadata:
|
|
389
|
+
name: servicenow-mcp
|
|
390
|
+
spec:
|
|
391
|
+
replicas: 2
|
|
392
|
+
selector:
|
|
393
|
+
matchLabels:
|
|
394
|
+
app: servicenow-mcp
|
|
395
|
+
template:
|
|
396
|
+
metadata:
|
|
397
|
+
labels:
|
|
398
|
+
app: servicenow-mcp
|
|
399
|
+
spec:
|
|
400
|
+
containers:
|
|
401
|
+
- name: servicenow-mcp
|
|
402
|
+
image: nczitzer/mcp-servicenow-nodejs:latest
|
|
403
|
+
ports:
|
|
404
|
+
- containerPort: 3000
|
|
405
|
+
env:
|
|
406
|
+
- name: SSE_KEEPALIVE_INTERVAL
|
|
407
|
+
value: "10000"
|
|
408
|
+
- name: SERVICENOW_INSTANCE_URL
|
|
409
|
+
valueFrom:
|
|
410
|
+
secretKeyRef:
|
|
411
|
+
name: servicenow-creds
|
|
412
|
+
key: instance-url
|
|
413
|
+
livenessProbe:
|
|
414
|
+
httpGet:
|
|
415
|
+
path: /health
|
|
416
|
+
port: 3000
|
|
417
|
+
initialDelaySeconds: 10
|
|
418
|
+
periodSeconds: 30
|
|
419
|
+
readinessProbe:
|
|
420
|
+
httpGet:
|
|
421
|
+
path: /health
|
|
422
|
+
port: 3000
|
|
423
|
+
initialDelaySeconds: 5
|
|
424
|
+
periodSeconds: 10
|
|
425
|
+
---
|
|
426
|
+
apiVersion: v1
|
|
427
|
+
kind: Service
|
|
428
|
+
metadata:
|
|
429
|
+
name: servicenow-mcp
|
|
430
|
+
spec:
|
|
431
|
+
selector:
|
|
432
|
+
app: servicenow-mcp
|
|
433
|
+
ports:
|
|
434
|
+
- port: 3000
|
|
435
|
+
targetPort: 3000
|
|
436
|
+
sessionAffinity: ClientIP # Important for SSE!
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## ๐ Technical Details
|
|
442
|
+
|
|
443
|
+
### SSE Keepalive Implementation
|
|
444
|
+
|
|
445
|
+
The server sends SSE comments (lines starting with `:`) at regular intervals:
|
|
446
|
+
|
|
447
|
+
```
|
|
448
|
+
: keepalive
|
|
449
|
+
|
|
450
|
+
: keepalive
|
|
451
|
+
|
|
452
|
+
: keepalive
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
These comments:
|
|
456
|
+
- โ
Keep TCP connection alive
|
|
457
|
+
- โ
Prevent proxy timeouts
|
|
458
|
+
- โ
Don't trigger client events (invisible to app)
|
|
459
|
+
- โ
Follow SSE specification (RFC 6202)
|
|
460
|
+
|
|
461
|
+
### Connection Lifecycle
|
|
462
|
+
|
|
463
|
+
1. **Client connects** โ `GET /mcp`
|
|
464
|
+
2. **Server responds** โ Sets SSE headers, disables timeouts
|
|
465
|
+
3. **Transport starts** โ MCP server connects to SSE transport
|
|
466
|
+
4. **Keepalive begins** โ Interval sends comments every N seconds
|
|
467
|
+
5. **Client disconnects** โ Cleanup interval, delete session
|
|
468
|
+
|
|
469
|
+
### Performance Impact
|
|
470
|
+
|
|
471
|
+
**Network overhead:**
|
|
472
|
+
- Default (15s): ~4 keepalives/minute = ~240 bytes/min
|
|
473
|
+
- Aggressive (5s): ~12 keepalives/minute = ~720 bytes/min
|
|
474
|
+
|
|
475
|
+
**Recommendation:** Use default 15 seconds unless experiencing drops
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## โ
Best Practices
|
|
480
|
+
|
|
481
|
+
1. **Use default 15-second keepalive** unless you have specific timeout issues
|
|
482
|
+
2. **Configure proxy timeouts** > keepalive interval (e.g., 86400s for 24 hours)
|
|
483
|
+
3. **Enable session affinity** (sticky sessions) for load balancers
|
|
484
|
+
4. **Monitor connection logs** to detect issues early
|
|
485
|
+
5. **Test with curl** before deploying production clients
|
|
486
|
+
6. **Use Docker health checks** to verify server availability
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## ๐ Getting Help
|
|
491
|
+
|
|
492
|
+
**Connection issues?**
|
|
493
|
+
1. Check server logs: `docker logs servicenow-mcp-server`
|
|
494
|
+
2. Test with curl: `curl -N http://localhost:3000/mcp`
|
|
495
|
+
3. Verify keepalive: Look for `: keepalive` comments
|
|
496
|
+
4. Check proxy config: Ensure buffering disabled
|
|
497
|
+
|
|
498
|
+
**Still having problems?**
|
|
499
|
+
- GitHub Issues: https://github.com/Happy-Technologies-LLC/mcp-servicenow-nodejs/issues
|
|
500
|
+
- Include: Server logs, proxy config, keepalive interval
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## ๐ References
|
|
505
|
+
|
|
506
|
+
- **SSE Specification:** https://html.spec.whatwg.org/multipage/server-sent-events.html
|
|
507
|
+
- **MCP SSE Transport:** https://spec.modelcontextprotocol.io/specification/basic/transports/#server-sent-events-sse
|
|
508
|
+
- **nginx SSE Guide:** https://www.nginx.com/blog/event-driven-data-management-nginx/
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
**Version:** 2.1.4
|
|
513
|
+
**Updated:** 2025-11-19
|
|
514
|
+
**Author:** Happy Technologies LLC
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# SSE Connection Stability Fix - Summary
|
|
2
|
+
|
|
3
|
+
**Issue:** SSE connections dropping in Docker environments
|
|
4
|
+
**Status:** โ
FIXED
|
|
5
|
+
**Version:** 2.1.4+
|
|
6
|
+
**Date:** November 19, 2025
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Users reported SSE connections dropping when running the MCP server in Docker. The connection worked fine with Claude Desktop (stdio transport) but failed in Docker with SSE transport.
|
|
13
|
+
|
|
14
|
+
### Root Causes
|
|
15
|
+
|
|
16
|
+
1. **No keepalive mechanism** - Idle SSE connections timeout
|
|
17
|
+
2. **Proxy/load balancer timeouts** - nginx, HAProxy, etc. kill idle connections
|
|
18
|
+
3. **Docker network timeouts** - Docker networking closes idle TCP connections
|
|
19
|
+
4. **Express default timeouts** - 2-minute default timeout kills long-running connections
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Solution
|
|
24
|
+
|
|
25
|
+
Implemented automatic SSE keepalive heartbeat mechanism:
|
|
26
|
+
|
|
27
|
+
### What Was Fixed
|
|
28
|
+
|
|
29
|
+
โ
**Automatic Keepalive Heartbeat**
|
|
30
|
+
- Sends SSE comment every 15 seconds (default)
|
|
31
|
+
- Keeps connection alive without triggering client events
|
|
32
|
+
- Configurable via `SSE_KEEPALIVE_INTERVAL` environment variable
|
|
33
|
+
|
|
34
|
+
โ
**Disabled Timeouts**
|
|
35
|
+
- Set `req.setTimeout(0)` and `res.setTimeout(0)` for SSE endpoint
|
|
36
|
+
- Allows infinite connection duration
|
|
37
|
+
|
|
38
|
+
โ
**Proxy-Friendly Headers**
|
|
39
|
+
- Added `X-Accel-Buffering: no` for nginx
|
|
40
|
+
- Added `Cache-Control: no-cache, no-transform`
|
|
41
|
+
- Added `Connection: keep-alive`
|
|
42
|
+
|
|
43
|
+
โ
**Connection Monitoring**
|
|
44
|
+
- Automatic cleanup on disconnect/error
|
|
45
|
+
- Proper interval cleanup to prevent memory leaks
|
|
46
|
+
- Connection lifecycle logging
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## How to Use
|
|
51
|
+
|
|
52
|
+
### Quick Start
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
docker run -d \
|
|
56
|
+
--name servicenow-mcp \
|
|
57
|
+
-p 3000:3000 \
|
|
58
|
+
-e SERVICENOW_INSTANCE_URL=https://dev12345.service-now.com \
|
|
59
|
+
-e SERVICENOW_USERNAME=admin \
|
|
60
|
+
-e SERVICENOW_PASSWORD=password \
|
|
61
|
+
nczitzer/mcp-servicenow-nodejs:latest
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Test Connection
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Watch SSE stream (you should see keepalive comments every 15 seconds)
|
|
68
|
+
curl -N http://localhost:3000/mcp
|
|
69
|
+
|
|
70
|
+
# Expected output:
|
|
71
|
+
# event: endpoint
|
|
72
|
+
# data: /message
|
|
73
|
+
#
|
|
74
|
+
# : keepalive
|
|
75
|
+
#
|
|
76
|
+
# : keepalive
|
|
77
|
+
#
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Configure Keepalive Interval
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Default: 15 seconds (recommended)
|
|
84
|
+
docker run -e SSE_KEEPALIVE_INTERVAL=15000 ...
|
|
85
|
+
|
|
86
|
+
# Behind aggressive proxies: 10 seconds
|
|
87
|
+
docker run -e SSE_KEEPALIVE_INTERVAL=10000 ...
|
|
88
|
+
|
|
89
|
+
# Low-latency networks: 30 seconds
|
|
90
|
+
docker run -e SSE_KEEPALIVE_INTERVAL=30000 ...
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## nginx Configuration
|
|
96
|
+
|
|
97
|
+
If using nginx as reverse proxy:
|
|
98
|
+
|
|
99
|
+
```nginx
|
|
100
|
+
location /mcp {
|
|
101
|
+
proxy_pass http://servicenow-mcp:3000;
|
|
102
|
+
|
|
103
|
+
# CRITICAL: Disable buffering for SSE
|
|
104
|
+
proxy_buffering off;
|
|
105
|
+
proxy_cache off;
|
|
106
|
+
proxy_read_timeout 86400s; # 24 hours
|
|
107
|
+
|
|
108
|
+
# SSE headers
|
|
109
|
+
proxy_set_header Connection '';
|
|
110
|
+
proxy_set_header Cache-Control 'no-cache';
|
|
111
|
+
proxy_set_header X-Accel-Buffering 'no';
|
|
112
|
+
|
|
113
|
+
proxy_http_version 1.1;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Verification
|
|
120
|
+
|
|
121
|
+
### Server Logs
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
docker logs servicenow-mcp-server
|
|
125
|
+
|
|
126
|
+
# Look for:
|
|
127
|
+
๐ ServiceNow MCP Server listening on port 3000
|
|
128
|
+
๐ SSE keepalive interval: 15000ms
|
|
129
|
+
๐ New session established: <session-id>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Connection Test
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Terminal 1: Start server
|
|
136
|
+
docker-compose up
|
|
137
|
+
|
|
138
|
+
# Terminal 2: Test connection (watch for keepalive comments)
|
|
139
|
+
curl -N http://localhost:3000/mcp
|
|
140
|
+
|
|
141
|
+
# Terminal 3: Monitor logs
|
|
142
|
+
docker logs -f servicenow-mcp-server
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Files Changed
|
|
148
|
+
|
|
149
|
+
- `src/server.js` - Added keepalive mechanism, timeout handling, connection monitoring
|
|
150
|
+
- `docker-compose.yml` - Added SSE_KEEPALIVE_INTERVAL environment variable
|
|
151
|
+
- `docs/SSE_DOCKER_SETUP.md` - Comprehensive setup guide
|
|
152
|
+
- `examples/test-sse-keepalive.js` - Standalone test script
|
|
153
|
+
- `README.md` - Updated with v2.1.4 features
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Technical Details
|
|
158
|
+
|
|
159
|
+
### Keepalive Implementation
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
// Send SSE comment every 15 seconds (configurable)
|
|
163
|
+
const keepaliveInterval = setInterval(() => {
|
|
164
|
+
try {
|
|
165
|
+
res.write(': keepalive\n\n');
|
|
166
|
+
} catch (error) {
|
|
167
|
+
clearInterval(keepaliveInterval);
|
|
168
|
+
}
|
|
169
|
+
}, SSE_KEEPALIVE_INTERVAL);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### SSE Comments
|
|
173
|
+
|
|
174
|
+
SSE comments (lines starting with `:`) are:
|
|
175
|
+
- Invisible to client applications
|
|
176
|
+
- Keep TCP connection alive
|
|
177
|
+
- Prevent proxy timeouts
|
|
178
|
+
- Follow SSE specification (RFC 6202)
|
|
179
|
+
|
|
180
|
+
### Performance Impact
|
|
181
|
+
|
|
182
|
+
**Network overhead:**
|
|
183
|
+
- Default (15s): ~240 bytes/minute
|
|
184
|
+
- Aggressive (5s): ~720 bytes/minute
|
|
185
|
+
|
|
186
|
+
Recommendation: Use default 15 seconds
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Before vs After
|
|
191
|
+
|
|
192
|
+
### Before (v2.1.3 and earlier)
|
|
193
|
+
|
|
194
|
+
โ Connections dropped after 60-120 seconds
|
|
195
|
+
โ No keepalive mechanism
|
|
196
|
+
โ Express default 2-minute timeout
|
|
197
|
+
โ Proxies timed out idle connections
|
|
198
|
+
|
|
199
|
+
### After (v2.1.4+)
|
|
200
|
+
|
|
201
|
+
โ
Stable long-running connections
|
|
202
|
+
โ
Automatic keepalive every 15 seconds
|
|
203
|
+
โ
Infinite timeout for SSE endpoint
|
|
204
|
+
โ
Proxy-friendly headers
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Troubleshooting
|
|
209
|
+
|
|
210
|
+
**Still dropping?**
|
|
211
|
+
|
|
212
|
+
1. Check keepalive interval: `docker logs servicenow-mcp-server | grep keepalive`
|
|
213
|
+
2. Monitor connection: `curl -N http://localhost:3000/mcp`
|
|
214
|
+
3. Reduce interval: `SSE_KEEPALIVE_INTERVAL=10000`
|
|
215
|
+
4. Check proxy timeout: Must be > keepalive interval
|
|
216
|
+
|
|
217
|
+
**See full troubleshooting guide:** `docs/SSE_DOCKER_SETUP.md`
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Migration Guide
|
|
222
|
+
|
|
223
|
+
### Existing Deployments
|
|
224
|
+
|
|
225
|
+
**No changes required!** The fix is automatic.
|
|
226
|
+
|
|
227
|
+
**Optional tuning:**
|
|
228
|
+
|
|
229
|
+
```yaml
|
|
230
|
+
# docker-compose.yml
|
|
231
|
+
services:
|
|
232
|
+
servicenow-mcp-server:
|
|
233
|
+
environment:
|
|
234
|
+
- SSE_KEEPALIVE_INTERVAL=15000 # Optional: default is 15s
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Kubernetes
|
|
238
|
+
|
|
239
|
+
```yaml
|
|
240
|
+
env:
|
|
241
|
+
- name: SSE_KEEPALIVE_INTERVAL
|
|
242
|
+
value: "10000" # 10 seconds for K8s environments
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Testing
|
|
248
|
+
|
|
249
|
+
All 183 tests pass:
|
|
250
|
+
- โ
Unit tests
|
|
251
|
+
- โ
Integration tests
|
|
252
|
+
- โ
Production validation
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Credits
|
|
257
|
+
|
|
258
|
+
**Issue Reported By:** Community user
|
|
259
|
+
**Fixed By:** Claude Code
|
|
260
|
+
**Version:** 2.1.4
|
|
261
|
+
**Release Date:** November 19, 2025
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Next Steps
|
|
266
|
+
|
|
267
|
+
1. Pull latest image: `docker pull nczitzer/mcp-servicenow-nodejs:latest`
|
|
268
|
+
2. Update deployment with new environment variable (optional)
|
|
269
|
+
3. Test SSE connection: `curl -N http://localhost:3000/mcp`
|
|
270
|
+
4. Monitor logs for keepalive messages
|
|
271
|
+
5. Enjoy stable connections! ๐
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
**Full Documentation:** `docs/SSE_DOCKER_SETUP.md`
|
|
276
|
+
**GitHub:** https://github.com/Happy-Technologies-LLC/mcp-servicenow-nodejs
|
|
277
|
+
**npm:** https://www.npmjs.com/package/servicenow-mcp-server
|
package/package.json
CHANGED
package/src/server.js
CHANGED
|
@@ -15,6 +15,9 @@ import { configManager } from './config-manager.js';
|
|
|
15
15
|
// Load environment variables
|
|
16
16
|
dotenv.config();
|
|
17
17
|
|
|
18
|
+
// SSE configuration
|
|
19
|
+
const SSE_KEEPALIVE_INTERVAL = parseInt(process.env.SSE_KEEPALIVE_INTERVAL || '15000', 10); // Default: 15 seconds
|
|
20
|
+
|
|
18
21
|
const app = express();
|
|
19
22
|
app.use(express.json());
|
|
20
23
|
|
|
@@ -39,22 +42,58 @@ serviceNowClient.currentInstanceName = defaultInstance.name;
|
|
|
39
42
|
*/
|
|
40
43
|
app.get('/mcp', async (req, res) => {
|
|
41
44
|
try {
|
|
45
|
+
// SSE-specific headers to prevent buffering and timeouts
|
|
46
|
+
res.setHeader('Cache-Control', 'no-cache, no-transform');
|
|
47
|
+
res.setHeader('X-Accel-Buffering', 'no'); // Disable nginx buffering
|
|
48
|
+
res.setHeader('Connection', 'keep-alive');
|
|
49
|
+
|
|
50
|
+
// Disable timeout for SSE endpoint (0 = infinite)
|
|
51
|
+
req.setTimeout(0);
|
|
52
|
+
res.setTimeout(0);
|
|
53
|
+
|
|
42
54
|
// Create transport and start SSE connection
|
|
43
55
|
const transport = new SSEServerTransport('/mcp', res);
|
|
44
56
|
|
|
45
57
|
// Create and configure new MCP server instance
|
|
46
58
|
const server = await createMcpServer(serviceNowClient);
|
|
47
59
|
|
|
60
|
+
// Set up keepalive heartbeat to prevent connection timeout
|
|
61
|
+
// Send a comment every N seconds to keep connection alive
|
|
62
|
+
const keepaliveInterval = setInterval(() => {
|
|
63
|
+
try {
|
|
64
|
+
// Send SSE comment (starts with :) to keep connection alive
|
|
65
|
+
res.write(': keepalive\n\n');
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('โ Keepalive failed, clearing interval:', error.message);
|
|
68
|
+
clearInterval(keepaliveInterval);
|
|
69
|
+
}
|
|
70
|
+
}, SSE_KEEPALIVE_INTERVAL);
|
|
71
|
+
|
|
48
72
|
// Set up transport cleanup
|
|
49
73
|
transport.onclose = () => {
|
|
50
74
|
if (sessions[transport.sessionId]) {
|
|
75
|
+
clearInterval(keepaliveInterval);
|
|
51
76
|
delete sessions[transport.sessionId];
|
|
52
77
|
console.log(`๐งน Cleaned up session ${transport.sessionId}`);
|
|
53
78
|
}
|
|
54
79
|
};
|
|
55
80
|
|
|
81
|
+
// Clean up on request close/error
|
|
82
|
+
req.on('close', () => {
|
|
83
|
+
clearInterval(keepaliveInterval);
|
|
84
|
+
if (sessions[transport.sessionId]) {
|
|
85
|
+
delete sessions[transport.sessionId];
|
|
86
|
+
console.log(`๐ Client disconnected: ${transport.sessionId}`);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
req.on('error', (error) => {
|
|
91
|
+
console.error('โ Request error:', error);
|
|
92
|
+
clearInterval(keepaliveInterval);
|
|
93
|
+
});
|
|
94
|
+
|
|
56
95
|
// Store the session
|
|
57
|
-
sessions[transport.sessionId] = { server, transport };
|
|
96
|
+
sessions[transport.sessionId] = { server, transport, keepaliveInterval };
|
|
58
97
|
console.log(`๐ New session established: ${transport.sessionId}`);
|
|
59
98
|
|
|
60
99
|
// Connect server to transport and start SSE
|
|
@@ -63,7 +102,9 @@ app.get('/mcp', async (req, res) => {
|
|
|
63
102
|
|
|
64
103
|
} catch (error) {
|
|
65
104
|
console.error('โ Error establishing SSE connection:', error);
|
|
66
|
-
res.
|
|
105
|
+
if (!res.headersSent) {
|
|
106
|
+
res.status(500).json({ error: 'Failed to establish SSE connection' });
|
|
107
|
+
}
|
|
67
108
|
}
|
|
68
109
|
});
|
|
69
110
|
|
|
@@ -112,14 +153,14 @@ app.get('/instances', (req, res) => {
|
|
|
112
153
|
// Start the server
|
|
113
154
|
const PORT = process.env.PORT || 3000;
|
|
114
155
|
app.listen(PORT, () => {
|
|
115
|
-
console.log(
|
|
116
|
-
console.log(
|
|
117
|
-
console.log(
|
|
118
|
-
|
|
119
|
-
console.log(
|
|
156
|
+
console.log(`๐ ServiceNow MCP Server listening on port ${PORT}`);
|
|
157
|
+
console.log(`๐ Health check: http://localhost:${PORT}/health`);
|
|
158
|
+
console.log(`๐ MCP SSE endpoint: http://localhost:${PORT}/mcp`);
|
|
159
|
+
console.log(`๐ Available instances: http://localhost:${PORT}/instances`);
|
|
160
|
+
console.log(`๐ SSE keepalive interval: ${SSE_KEEPALIVE_INTERVAL}ms`);
|
|
120
161
|
|
|
121
162
|
if (process.env.DEBUG === 'true') {
|
|
122
|
-
console.log('Debug mode enabled');
|
|
123
|
-
console.log(
|
|
163
|
+
console.log('๐ Debug mode enabled');
|
|
164
|
+
console.log(`๐ Active ServiceNow instance: ${defaultInstance.name} - ${defaultInstance.url}`);
|
|
124
165
|
}
|
|
125
166
|
});
|