@pmoses-s1/sentinelone-mcp 1.0.0 → 1.2.0
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/CHANGELOG.md +60 -0
- package/README.md +422 -102
- package/deploy/README.md +366 -0
- package/deploy/bridge/README.md +93 -0
- package/deploy/bridge/sentinelone-mcp-bridge.mjs +104 -0
- package/deploy/caddy/Caddyfile.example +110 -0
- package/deploy/install.sh +264 -0
- package/deploy/systemd/sentinelone-mcp.service +58 -0
- package/index.js +135 -312
- package/lib/auth.js +161 -0
- package/lib/credentials.js +18 -7
- package/lib/hec.js +128 -0
- package/lib/http-transport.js +223 -0
- package/lib/s1.js +1 -1
- package/lib/sdl.js +0 -32
- package/lib/server-core.js +264 -0
- package/lib/stdio-transport.js +77 -0
- package/lib/uam-ingest.js +1 -1
- package/package.json +10 -4
- package/scripts/regen-readme-tools-table.mjs +142 -0
- package/scripts/smoke-test-http.sh +122 -0
- package/scripts/test-mac.sh +179 -0
- package/tools/hyperautomation.js +1 -1
- package/tools/mgmt-console.js +30 -3
- package/tools/sdl-api.js +16 -24
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# sentinelone-mcp installer for macOS and Linux.
|
|
4
|
+
#
|
|
5
|
+
# Modes:
|
|
6
|
+
# --user (default) Install for the current user only.
|
|
7
|
+
# Writes credentials to ~/.config/sentinelone/credentials.json,
|
|
8
|
+
# installs the npm package globally via the current Node toolchain.
|
|
9
|
+
#
|
|
10
|
+
# --server Linux VM deployment. Creates a system `mcp` user, installs the
|
|
11
|
+
# npm package globally, writes credentials and bearer tokens to
|
|
12
|
+
# /etc/sentinelone-mcp/, drops the systemd unit, enables and
|
|
13
|
+
# starts the service.
|
|
14
|
+
#
|
|
15
|
+
# Idempotent: rerunning is safe; it skips steps already completed.
|
|
16
|
+
#
|
|
17
|
+
# Exit codes: 0 ok, 1 generic failure, 2 unsupported platform, 3 missing prereq.
|
|
18
|
+
|
|
19
|
+
set -euo pipefail
|
|
20
|
+
|
|
21
|
+
# ─── helpers ─────────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
c_red() { printf '\033[31m%s\033[0m\n' "$*"; }
|
|
24
|
+
c_green() { printf '\033[32m%s\033[0m\n' "$*"; }
|
|
25
|
+
c_yellow() { printf '\033[33m%s\033[0m\n' "$*"; }
|
|
26
|
+
c_bold() { printf '\033[1m%s\033[0m\n' "$*"; }
|
|
27
|
+
|
|
28
|
+
step() { c_bold ">> $*"; }
|
|
29
|
+
ok() { c_green " ok: $*"; }
|
|
30
|
+
warn() { c_yellow " warn: $*"; }
|
|
31
|
+
die() { c_red " error: $*"; exit 1; }
|
|
32
|
+
|
|
33
|
+
PKG="@pmoses-s1/sentinelone-mcp"
|
|
34
|
+
MODE="user"
|
|
35
|
+
|
|
36
|
+
while [[ $# -gt 0 ]]; do
|
|
37
|
+
case "$1" in
|
|
38
|
+
--user) MODE="user"; shift ;;
|
|
39
|
+
--server) MODE="server"; shift ;;
|
|
40
|
+
-h|--help)
|
|
41
|
+
cat <<EOF
|
|
42
|
+
Usage: $0 [--user|--server]
|
|
43
|
+
|
|
44
|
+
--user Install for current user (default).
|
|
45
|
+
Default install path on macOS: ~/.config/sentinelone/
|
|
46
|
+
Default install path on Linux: ~/.config/sentinelone/
|
|
47
|
+
|
|
48
|
+
--server Install on a Linux VM as a shared service.
|
|
49
|
+
System path: /etc/sentinelone-mcp/
|
|
50
|
+
systemd unit: sentinelone-mcp.service
|
|
51
|
+
Requires sudo.
|
|
52
|
+
|
|
53
|
+
EOF
|
|
54
|
+
exit 0 ;;
|
|
55
|
+
*) die "Unknown flag: $1 (try --help)" ;;
|
|
56
|
+
esac
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
OS="$(uname -s)"
|
|
60
|
+
case "$OS" in
|
|
61
|
+
Darwin) PLATFORM="mac" ;;
|
|
62
|
+
Linux) PLATFORM="linux" ;;
|
|
63
|
+
*) c_red "Unsupported platform: $OS"; exit 2 ;;
|
|
64
|
+
esac
|
|
65
|
+
|
|
66
|
+
if [[ "$MODE" == "server" && "$PLATFORM" != "linux" ]]; then
|
|
67
|
+
die "--server mode is Linux only (got $PLATFORM)"
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# ─── prereqs ─────────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
step "Checking prerequisites"
|
|
73
|
+
|
|
74
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
75
|
+
c_red "Node.js is required but not found on PATH."
|
|
76
|
+
c_red "Install Node 18+:"
|
|
77
|
+
c_red " macOS: brew install node@20"
|
|
78
|
+
c_red " Linux: curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - && sudo apt install -y nodejs"
|
|
79
|
+
exit 3
|
|
80
|
+
fi
|
|
81
|
+
NODE_MAJOR="$(node --version | sed 's/^v\([0-9]*\).*/\1/')"
|
|
82
|
+
if [[ "$NODE_MAJOR" -lt 18 ]]; then
|
|
83
|
+
die "Node $(node --version) is too old. Need Node 18+."
|
|
84
|
+
fi
|
|
85
|
+
ok "node $(node --version)"
|
|
86
|
+
|
|
87
|
+
if ! command -v npm >/dev/null 2>&1; then
|
|
88
|
+
die "npm not found alongside node; please install Node 18+ from nodejs.org or your package manager."
|
|
89
|
+
fi
|
|
90
|
+
ok "npm $(npm --version)"
|
|
91
|
+
|
|
92
|
+
if [[ "$MODE" == "server" ]]; then
|
|
93
|
+
if [[ "$EUID" -ne 0 ]]; then
|
|
94
|
+
die "--server mode must be run with sudo (need to create /etc/sentinelone-mcp/, system user, and systemd unit)."
|
|
95
|
+
fi
|
|
96
|
+
command -v systemctl >/dev/null 2>&1 || die "systemctl not found; this script targets systemd-based Linux."
|
|
97
|
+
ok "running as root, systemd present"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# ─── install package ─────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
step "Installing $PKG globally"
|
|
103
|
+
if [[ "$MODE" == "server" ]]; then
|
|
104
|
+
npm install -g "$PKG" >/dev/null
|
|
105
|
+
else
|
|
106
|
+
# Avoid sudo on Mac/personal Linux: use a per-user npm prefix if not already.
|
|
107
|
+
if ! npm config get prefix --location=user 2>/dev/null | grep -qE '^/'; then
|
|
108
|
+
npm config set prefix "$HOME/.npm-global"
|
|
109
|
+
case ":$PATH:" in
|
|
110
|
+
*":$HOME/.npm-global/bin:"*) ;;
|
|
111
|
+
*) warn "Add $HOME/.npm-global/bin to your PATH (currently missing)." ;;
|
|
112
|
+
esac
|
|
113
|
+
fi
|
|
114
|
+
npm install -g "$PKG" >/dev/null
|
|
115
|
+
fi
|
|
116
|
+
ok "$(npm ls -g --depth=0 "$PKG" 2>/dev/null | grep "$PKG" | head -1 | sed 's/.*-> //' || echo installed)"
|
|
117
|
+
|
|
118
|
+
# ─── credentials skeleton ────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
if [[ "$MODE" == "server" ]]; then
|
|
121
|
+
CONF_DIR="/etc/sentinelone-mcp"
|
|
122
|
+
CRED_PATH="$CONF_DIR/credentials.json"
|
|
123
|
+
TOKEN_PATH="$CONF_DIR/bearer-tokens.json"
|
|
124
|
+
ENV_PATH="$CONF_DIR/server.env"
|
|
125
|
+
OWNER="mcp"
|
|
126
|
+
else
|
|
127
|
+
CONF_DIR="$HOME/.config/sentinelone"
|
|
128
|
+
CRED_PATH="$CONF_DIR/credentials.json"
|
|
129
|
+
TOKEN_PATH=""
|
|
130
|
+
ENV_PATH=""
|
|
131
|
+
OWNER="$USER"
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
step "Setting up $CONF_DIR"
|
|
135
|
+
mkdir -p "$CONF_DIR"
|
|
136
|
+
if [[ ! -f "$CRED_PATH" ]]; then
|
|
137
|
+
cat > "$CRED_PATH" <<'EOF'
|
|
138
|
+
{
|
|
139
|
+
"S1_CONSOLE_URL": "https://usea1-acme.sentinelone.net",
|
|
140
|
+
"S1_CONSOLE_API_TOKEN": "REPLACE_WITH_API_TOKEN",
|
|
141
|
+
"S1_HEC_INGEST_URL": "https://ingest.us1.sentinelone.net",
|
|
142
|
+
"SDL_XDR_URL": "https://xdr.us1.sentinelone.net",
|
|
143
|
+
"SDL_LOG_READ_KEY": "",
|
|
144
|
+
"SDL_CONFIG_READ_KEY": "",
|
|
145
|
+
"SDL_CONFIG_WRITE_KEY": ""
|
|
146
|
+
}
|
|
147
|
+
EOF
|
|
148
|
+
chmod 600 "$CRED_PATH"
|
|
149
|
+
ok "wrote $CRED_PATH (placeholder, edit before starting)"
|
|
150
|
+
else
|
|
151
|
+
ok "$CRED_PATH already exists, leaving untouched"
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
if [[ "$MODE" == "server" ]]; then
|
|
155
|
+
if ! id "$OWNER" >/dev/null 2>&1; then
|
|
156
|
+
step "Creating system user '$OWNER'"
|
|
157
|
+
useradd --system --no-create-home --shell /usr/sbin/nologin "$OWNER"
|
|
158
|
+
ok "created"
|
|
159
|
+
else
|
|
160
|
+
ok "user '$OWNER' already exists"
|
|
161
|
+
fi
|
|
162
|
+
chown -R "$OWNER":"$OWNER" "$CONF_DIR"
|
|
163
|
+
chmod 600 "$CRED_PATH"
|
|
164
|
+
|
|
165
|
+
if [[ ! -f "$TOKEN_PATH" ]]; then
|
|
166
|
+
step "Generating initial bearer token"
|
|
167
|
+
if command -v openssl >/dev/null 2>&1; then
|
|
168
|
+
TOKEN_ADMIN="$(openssl rand -hex 32)"
|
|
169
|
+
else
|
|
170
|
+
TOKEN_ADMIN="$(node -e 'console.log(require("crypto").randomBytes(32).toString("hex"))')"
|
|
171
|
+
fi
|
|
172
|
+
cat > "$TOKEN_PATH" <<EOF
|
|
173
|
+
{
|
|
174
|
+
"admin": "$TOKEN_ADMIN"
|
|
175
|
+
}
|
|
176
|
+
EOF
|
|
177
|
+
chmod 600 "$TOKEN_PATH"
|
|
178
|
+
chown "$OWNER":"$OWNER" "$TOKEN_PATH"
|
|
179
|
+
ok "wrote $TOKEN_PATH (one initial admin token)"
|
|
180
|
+
c_yellow " INITIAL ADMIN BEARER TOKEN:"
|
|
181
|
+
c_yellow " $TOKEN_ADMIN"
|
|
182
|
+
c_yellow " Save this value now; it is also stored in $TOKEN_PATH."
|
|
183
|
+
else
|
|
184
|
+
ok "$TOKEN_PATH already exists"
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
if [[ ! -f "$ENV_PATH" ]]; then
|
|
188
|
+
cat > "$ENV_PATH" <<EOF
|
|
189
|
+
# Environment file for sentinelone-mcp.service.
|
|
190
|
+
# Adjust LOG_LEVEL or override anything here; reload with: systemctl reload sentinelone-mcp
|
|
191
|
+
EOF
|
|
192
|
+
chmod 600 "$ENV_PATH"
|
|
193
|
+
chown "$OWNER":"$OWNER" "$ENV_PATH"
|
|
194
|
+
ok "wrote $ENV_PATH"
|
|
195
|
+
else
|
|
196
|
+
ok "$ENV_PATH already exists"
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
step "Installing systemd unit"
|
|
200
|
+
SVC_PATH="/etc/systemd/system/sentinelone-mcp.service"
|
|
201
|
+
GLOBAL_NODE_MODULES="$(npm root -g)"
|
|
202
|
+
SCRIPT_DIR="$GLOBAL_NODE_MODULES/$PKG"
|
|
203
|
+
# Rewrite the ExecStart path to point at the resolved global install,
|
|
204
|
+
# since the bundled unit uses %h which assumes per-user install.
|
|
205
|
+
sed "s|%h/.npm-global/lib/node_modules/@pmoses-s1/sentinelone-mcp|$SCRIPT_DIR|g" \
|
|
206
|
+
"$SCRIPT_DIR/deploy/systemd/sentinelone-mcp.service" > "$SVC_PATH"
|
|
207
|
+
systemctl daemon-reload
|
|
208
|
+
systemctl enable sentinelone-mcp >/dev/null 2>&1
|
|
209
|
+
ok "wrote $SVC_PATH and enabled the service"
|
|
210
|
+
|
|
211
|
+
step "Starting the service"
|
|
212
|
+
if systemctl is-active sentinelone-mcp >/dev/null 2>&1; then
|
|
213
|
+
systemctl restart sentinelone-mcp
|
|
214
|
+
ok "restarted"
|
|
215
|
+
else
|
|
216
|
+
systemctl start sentinelone-mcp
|
|
217
|
+
ok "started"
|
|
218
|
+
fi
|
|
219
|
+
sleep 1
|
|
220
|
+
if systemctl is-active --quiet sentinelone-mcp; then
|
|
221
|
+
ok "service is active"
|
|
222
|
+
else
|
|
223
|
+
c_red "service failed to start. Recent log lines:"
|
|
224
|
+
journalctl -u sentinelone-mcp -n 30 --no-pager | sed 's/^/ /'
|
|
225
|
+
exit 1
|
|
226
|
+
fi
|
|
227
|
+
fi
|
|
228
|
+
|
|
229
|
+
# ─── final notes ─────────────────────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
step "Next steps"
|
|
232
|
+
if [[ "$MODE" == "user" ]]; then
|
|
233
|
+
cat <<EOF
|
|
234
|
+
|
|
235
|
+
1. Edit $CRED_PATH with your real SentinelOne values.
|
|
236
|
+
2. Try the server:
|
|
237
|
+
sentinelone-mcp --version
|
|
238
|
+
sentinelone-mcp --help
|
|
239
|
+
3. Wire it into Claude Cowork / Claude Desktop / Claude Code via stdio
|
|
240
|
+
(no HTTP needed for single-user local). See deploy/README.md for the
|
|
241
|
+
exact config block.
|
|
242
|
+
|
|
243
|
+
EOF
|
|
244
|
+
elif [[ "$MODE" == "server" ]]; then
|
|
245
|
+
cat <<EOF
|
|
246
|
+
|
|
247
|
+
1. Edit $CRED_PATH with your real SentinelOne values, then reload:
|
|
248
|
+
sudo systemctl reload sentinelone-mcp
|
|
249
|
+
2. Verify the server is up:
|
|
250
|
+
curl -s http://127.0.0.1:8765/healthz
|
|
251
|
+
3. Put TLS in front (Caddy template at $SCRIPT_DIR/deploy/caddy/Caddyfile.example).
|
|
252
|
+
4. Add team members by editing $TOKEN_PATH and reloading:
|
|
253
|
+
echo '{"admin":"...", "alice":"...", "bob":"..."}' > $TOKEN_PATH
|
|
254
|
+
sudo systemctl reload sentinelone-mcp
|
|
255
|
+
(Reload sends SIGHUP; no connection drops.)
|
|
256
|
+
5. Tail the audit log:
|
|
257
|
+
sudo journalctl -u sentinelone-mcp -f | grep '\[audit\]'
|
|
258
|
+
|
|
259
|
+
See deploy/README.md for the full Linux VM walkthrough.
|
|
260
|
+
|
|
261
|
+
EOF
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
c_green "Done."
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=SentinelOne MCP server (Streamable HTTP, team-shared)
|
|
3
|
+
Documentation=https://github.com/pmoses-s1/claude-skills/tree/main/sentinelone-mcp
|
|
4
|
+
After=network-online.target
|
|
5
|
+
Wants=network-online.target
|
|
6
|
+
|
|
7
|
+
[Service]
|
|
8
|
+
Type=simple
|
|
9
|
+
User=mcp
|
|
10
|
+
Group=mcp
|
|
11
|
+
|
|
12
|
+
# Server credentials (the S1 service-user token + SDL keys) and bearer tokens.
|
|
13
|
+
# Both are written by deploy/install.sh; rotate by editing then `systemctl reload`
|
|
14
|
+
# (which sends SIGHUP and reloads bearer tokens without dropping connections).
|
|
15
|
+
EnvironmentFile=/etc/sentinelone-mcp/server.env
|
|
16
|
+
Environment=MCP_BEARER_TOKENS_FILE=/etc/sentinelone-mcp/bearer-tokens.json
|
|
17
|
+
Environment=S1_CREDS_FILE=/etc/sentinelone-mcp/credentials.json
|
|
18
|
+
|
|
19
|
+
ExecStart=/usr/bin/env node %h/.npm-global/lib/node_modules/@pmoses-s1/sentinelone-mcp/index.js \
|
|
20
|
+
--transport http \
|
|
21
|
+
--host 127.0.0.1 \
|
|
22
|
+
--port 8765 \
|
|
23
|
+
--path /mcp
|
|
24
|
+
|
|
25
|
+
# SIGHUP reloads the bearer token file without restart.
|
|
26
|
+
ExecReload=/bin/kill -HUP $MAINPID
|
|
27
|
+
|
|
28
|
+
Restart=on-failure
|
|
29
|
+
RestartSec=5
|
|
30
|
+
|
|
31
|
+
# Hardening
|
|
32
|
+
# NOTE: MemoryDenyWriteExecute=true and LockPersonality=true are incompatible
|
|
33
|
+
# with Node.js V8 JIT (W+X mappings). Including them causes a silent SIGTRAP
|
|
34
|
+
# at startup with no useful log output. Leave them off for Node services.
|
|
35
|
+
NoNewPrivileges=true
|
|
36
|
+
PrivateTmp=true
|
|
37
|
+
ProtectSystem=strict
|
|
38
|
+
ProtectHome=true
|
|
39
|
+
ProtectKernelTunables=true
|
|
40
|
+
ProtectKernelModules=true
|
|
41
|
+
ProtectControlGroups=true
|
|
42
|
+
RestrictNamespaces=true
|
|
43
|
+
RestrictRealtime=true
|
|
44
|
+
RestrictSUIDSGID=true
|
|
45
|
+
SystemCallArchitectures=native
|
|
46
|
+
ReadWritePaths=
|
|
47
|
+
|
|
48
|
+
# Resource limits
|
|
49
|
+
LimitNOFILE=4096
|
|
50
|
+
TasksMax=64
|
|
51
|
+
|
|
52
|
+
# Logging
|
|
53
|
+
StandardOutput=journal
|
|
54
|
+
StandardError=journal
|
|
55
|
+
SyslogIdentifier=sentinelone-mcp
|
|
56
|
+
|
|
57
|
+
[Install]
|
|
58
|
+
WantedBy=multi-user.target
|