@pmoses-s1/s1-secops-mcp 1.3.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 +119 -0
- package/README.md +519 -0
- package/deploy/README.md +370 -0
- package/deploy/bridge/README.md +93 -0
- package/deploy/bridge/sentinelone-mcp-bridge.mjs +122 -0
- package/deploy/caddy/Caddyfile.example +110 -0
- package/deploy/install.sh +280 -0
- package/deploy/systemd/s1-secops-mcp.service +59 -0
- package/index.js +171 -0
- package/lib/auth.js +161 -0
- package/lib/credentials.js +124 -0
- package/lib/hec.js +144 -0
- package/lib/http-transport.js +289 -0
- package/lib/s1.js +610 -0
- package/lib/sdl.js +130 -0
- package/lib/server-core.js +263 -0
- package/lib/stdio-transport.js +77 -0
- package/lib/uam-ingest.js +444 -0
- package/package.json +50 -0
- package/scripts/regen-readme-tools-table.mjs +142 -0
- package/scripts/smoke-test-http.sh +125 -0
- package/scripts/test-mac.sh +187 -0
- package/tools/hyperautomation.js +284 -0
- package/tools/mgmt-console.js +344 -0
- package/tools/powerquery.js +129 -0
- package/tools/sdl-api.js +125 -0
- package/tools/uam-ingest.js +128 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.2.4 - 2026-07-31
|
|
4
|
+
|
|
5
|
+
Hardening release from the 2026-07-31 code review. Tool count unchanged at 26.
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Missing `Retry-After` header no longer sleeps 0ms before retrying.** `Number(null)` is 0, so `lib/s1.js`, `lib/hec.js`, and `lib/uam-ingest.js` treated an absent header as "wait 0ms" and hammered the backend. All three now use the validated pattern from `lib/sdl.js`: honor the header only when present and parseable as a finite number of seconds (capped at 30s), otherwise fall back to the exponential delay. Numeric headers behave exactly as before.
|
|
9
|
+
- **`uam_set_status` no longer reports silent success.** The `alertTriggerActions` mutation selected only `__typename`, so a skipped or failed action still returned as if it worked (observed live: status unchanged after a "successful" call). The selection now mirrors the full `actions { success failure skip }` shape and the client throws when the backend reports a failure entry, skips the action without a success, or returns an empty actions list (nothing applied, e.g. the filter matched no alert). `uam_add_note` was audited for the same pattern; it already verifies via the returned note list.
|
|
10
|
+
- **LRQ polling tolerates transient poll errors.** A single 429/5xx poll response used to throw and cancel the running query. Transient statuses now keep polling (interval doubles up to 5s) until the existing 5-minute deadline; other 4xx responses remain fatal.
|
|
11
|
+
- **`hasSdlCreds()` recognizes read-only key deployments.** It required `SDL_CONFIG_WRITE_KEY` or the console token; tenants configured with only `SDL_LOG_READ_KEY` / `SDL_CONFIG_READ_KEY` were reported as unconfigured. All chain keys now count.
|
|
12
|
+
- **HTTP transport: oversized bodies get their 413.** The request was destroyed before the response was written, so clients saw a connection reset instead of the 413 JSON error. The server now stops reading, sends the 413, then drops the connection after the response is flushed.
|
|
13
|
+
- **HTTP transport exits nonzero on any listen-time server error** (EACCES, EADDRNOTAVAIL, ...), not only EADDRINUSE, so systemd/Docker restart policies see the failure.
|
|
14
|
+
- **`powerquery_schema_discover` escapes backslashes before quotes** in the data-source name; quote-only escaping let a trailing backslash re-open the V1 filter string.
|
|
15
|
+
- **`s1_api_get` honors an inline `?isLegacy=` already present in the path** instead of appending a conflicting `isLegacy=false` query param, matching `_maybe_inject_islegacy` in the Python twin.
|
|
16
|
+
- **SDL `config_read` key chain reordered to least-privilege first** (config_read, config_write, console JWT), matching the Python `SDLClient` the header claims to mirror.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- **Removed dead exports `purpleAiQuery` and `purpleAiInvestigate`** from `lib/s1.js`; their MCP tools were removed 2026-05-03 (browser-session teamToken requirement) and nothing referenced the library functions. Corrected stale doc text that pointed at a nonexistent `uam_set_analyst_verdict` tool: the analyst verdict is set via a raw `alertTriggerActions` mutation with the `analystVerdictUpdate` action through `s1_api_post`.
|
|
20
|
+
- **Deploy docs: credentials.json changes require `systemctl restart`.** SIGHUP reload only re-reads bearer tokens; the installer output and systemd unit comment said reload was enough. `deploy/install.sh` also sets `umask 077` so token/credential files are never world-readable at creation (the explicit `chmod 600` lines remain).
|
|
21
|
+
- **Claude Desktop bridge hardening:** 120s fetch timeout (`AbortSignal.timeout`), notification responses are drained so keep-alive sockets are released, and the URL constant no longer shadows the global `URL` constructor.
|
|
22
|
+
- **Docker entrypoint passes extra args through** to the selected server binary (e.g. `s1-secops-mcp --transport http`); no-args behavior is unchanged.
|
|
23
|
+
- **`const status = response.error ? 200 : 200`** simplified; JSON-RPC errors still return HTTP 200 with an error envelope.
|
|
24
|
+
|
|
25
|
+
### Tests
|
|
26
|
+
- New `tests/regressions-2026-07-31.test.mjs` (mocked fetch, no network): missing `Retry-After` uses the exponential delay, `uamSetStatus` throws on failure/skip results, inline `?isLegacy=` is honored. Both regression suites are now part of `npm test`.
|
|
27
|
+
- Transport and smoke tests read the expected version from `package.json` instead of a hardcoded string.
|
|
28
|
+
|
|
29
|
+
## 1.2.3 - 2026-07-29
|
|
30
|
+
|
|
31
|
+
Correctness release from the 2026-07-29 defect review. Fixes two bugs that produced plausible-but-wrong query results, hardens the SDL auth chain and HTTP retry paths, corrects the HEC `/event` content type, and adds a regression suite. Tool count unchanged at 26. (The Docker bundle image moved to 1.2.4 pinning `S1_MCP_VERSION=1.2.3`; the image tag stays ahead of the npm tag as it has since the split.)
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- **`powerquery_run` no longer collapses a caller-supplied time window.** The old code overwrote BOTH `startTime` and `endTime` with the last-`hours` default whenever either was missing, so a startTime-only call silently ran over the last 24 hours. Each bound now defaults independently. Live A/B: a startTime-only 7.4-day query returned 73,755 events on the fixed server vs 12,911 (the 24h control) on the old one.
|
|
35
|
+
- **`powerquery_run` now reports `matchCount`.** It was read from the top level of the LRQ response and came back `null` on every call; it lives inside the `data` block on current engines.
|
|
36
|
+
- **SDL auth chain falls through on 401/403.** `lib/sdl.js` treated an auth failure on the first configured key as fatal even when a later key (e.g. the console JWT) would work. It now advances through the chain and raises only when exhausted.
|
|
37
|
+
- **HEC `/event` ingestion uses `application/json`,** so per-event `time` backdating is honored instead of the envelope being indexed as opaque text at receive time.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- **Write requests no longer auto-retry on 5xx.** Retry is restricted to idempotent methods (GET/HEAD); read-only POSTs (GraphQL queries, Purple AI launches) opt back in via `allowRetry`. HEC raw ingest no longer retries 5xx (no idempotency key); UAM ingest still retries because `metadata.uid` dedupes.
|
|
41
|
+
- **`Retry-After` parsing hardened:** an HTTP-date value no longer collapses to `sleep(NaN)`; waits are validated and capped at 30s.
|
|
42
|
+
- **`uam_add_note` returns the correct note** (matches by text, tiebreaks on newest `createdAt`) instead of assuming newest-last ordering.
|
|
43
|
+
- **`uam_post_alert` schema help corrected** to require `class_uid 99602001` (the S1 Security Alert extension class); generic OCSF `2002` is silently dropped by the stitcher.
|
|
44
|
+
- **`ha_export_workflow` no longer implies it returns the archive** (metadata only).
|
|
45
|
+
- **`powerquery_schema_discover` escapes single quotes** in the data-source name before building the V1 filter.
|
|
46
|
+
|
|
47
|
+
### Tests
|
|
48
|
+
- New `tests/regressions-2026-07-29.test.mjs` (mocked fetch, no network): independent time-bound defaults, `matchCount` extraction, SDL 401/403 fall-through, HEC per-endpoint content type, write-vs-read retry semantics.
|
|
49
|
+
|
|
50
|
+
## 1.2.2 - 2026-06-13
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
- **Renamed `ha_archive_workflow` to `ha_delete_workflow`.** The old tool hit `POST /hyper-automate/api/v1/workflows/archive`, which returns HTTP 500 on this tenant. The replacement uses the validated `DELETE /hyper-automate/api/v1/workflows/{id}` endpoint (a soft, recoverable delete equivalent to clicking Delete in the Hyperautomation UI). Scope the call with `accountIds` or `siteIds`; a 404 "Object not found" means the id is not under that scope or is already deleted. Updated `README.md`, the tools-table regenerator, and the smoke test in lockstep.
|
|
54
|
+
- **`powerquery_run` description now documents the `datasource` and `savelookup` capabilities** (querying SentinelOne-managed inventory such as assets/alerts/vulnerabilities/misconfigurations, and persisting a result as a reusable lookup table), pointing at the new `powerquery/references/datasource-command.md`.
|
|
55
|
+
|
|
56
|
+
### Notes
|
|
57
|
+
- Tool count unchanged at 26 (the Hyperautomation tool was renamed, not added or removed).
|
|
58
|
+
- `SERVER_INFO.version` bumped in lockstep with `package.json` (the drift that forced the 1.2.0 -> 1.2.1 re-release).
|
|
59
|
+
|
|
60
|
+
## 1.2.1 - 2026-06-11
|
|
61
|
+
|
|
62
|
+
Supersedes 1.2.0, which was deprecated on npm. The 1.2.0 build shipped with a stale internal `SERVER_INFO.version` of `1.1.0` despite a `1.2.0` package version, so the server announced the wrong version on `initialize`. 1.2.1 is identical in features and corrects the reported runtime version. The content below is unchanged from the 1.2.0 work.
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
- **`hec_ingest` tool**: raw-log/event ingestion into the Singularity Data Lake via the HEC (HTTP Event Collector) endpoint (`/services/collector/raw` and `/services/collector/event`). Supports `parser` (-> `?sourcetype=`), custom `fields` (query params), **required** `scope` (S1-Scope header), gzip compression, and `isParsed` (-> `?isParsed=true`, indexes already-structured JSON with no SDL parser). Replaces the removed `sdl_upload_logs`. Validated live across the full HEC matrix (both endpoints, gzip on/off, parser field extraction, multi-line, batched, reserved-field handling, scope enforcement, isParsed). Grounded in the S-26.1 HEC docs (p.4723-4726).
|
|
66
|
+
|
|
67
|
+
### Removed
|
|
68
|
+
- **`sdl_upload_logs` tool** plus the underlying SDL `uploadLogs`/`addEvents` library functions and `SDL_LOG_WRITE_KEY` plumbing. SDL raw-log ingestion moves to the HEC path (`hec_ingest`). The `sdl-api` skill is now query + configuration only; the `sdl-log-parser` validation loop uses HEC ingest.
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
- Tool count unchanged at 26 (removed `sdl_upload_logs`, added `hec_ingest`).
|
|
72
|
+
- Skill docs corrected: scheduled detection rules bind the Target Asset via `entityMappings` ("Entity column mapping"); the full scheduled-rule option set (UI <-> API) is catalogued in `powerquery/references/detection-rules.md`.
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
## 1.1.0 - 2026-05-28 (rebuilt 2026-05-31)
|
|
76
|
+
|
|
77
|
+
### Fixed (rebuild)
|
|
78
|
+
- **`s1_api_get` now auto-injects `isLegacy=false` for `/cloud-detection/rules` listings.** Without `isLegacy=false` the S1 API silently omits `queryType="scheduled"` PowerQuery rules from the response; no error, no warning, the response just lies by omission. The handler now guards against this when the caller forgets, and the tool description loudly flags the requirement. This eliminates the "I see zero scheduled detections" failure mode that was producing wrong verdicts when listing Custom Detection rules. Same `1.1.0` version per the rebuild request.
|
|
79
|
+
|
|
80
|
+
### Added
|
|
81
|
+
- **Streamable HTTP transport.** New `--transport http` mode (default stays `stdio`). Single-endpoint POST `/mcp` per the MCP 2024-11-05 spec, plus `/healthz` for load balancer probes. Implementation is pure `node:http`, no new dependencies.
|
|
82
|
+
- **Per-user bearer token auth.** New `MCP_BEARER_TOKENS_FILE` env var pointing at a `{ "<name>": "<token>" }` JSON file gives each team member a stable name in audit logs and supports rotation. SIGHUP reloads tokens without dropping connections. `MCP_BEARER_TOKENS` env var (comma-separated raw tokens) is a fallback for small or quick-test setups.
|
|
83
|
+
- **Audit logging.** Every authenticated HTTP request emits `[audit] <ts> | <name> | <method> | <param-summary> | <status>` to stderr; systemd captures it via journald.
|
|
84
|
+
- **`S1_CREDS_FILE` credential resolver.** Highest-priority explicit path for credentials, useful for VM deployments and secret-store integrations (Vault, Doppler, 1Password Connect, sealed-secrets).
|
|
85
|
+
- **Deploy artifacts** under `deploy/`:
|
|
86
|
+
- `install.sh`: one-shot installer for Mac and Linux. `--user` mode for individuals, `--server` mode for Linux VMs (creates `mcp` system user, generates an initial bearer token, installs systemd unit, starts the service).
|
|
87
|
+
- `systemd/s1-secops-mcp.service`: hardened unit with `NoNewPrivileges`, `ProtectSystem=strict`, `MemoryDenyWriteExecute`, SIGHUP-as-reload.
|
|
88
|
+
- `caddy/Caddyfile.example`: TLS reverse proxy template with bearer header gate and streaming-friendly flush.
|
|
89
|
+
- `README.md`: full topology guide (single-user local, single-user HTTP, team VM-hosted) with day-2 operations.
|
|
90
|
+
- **Test suite.** Three new files under `tests/`, runnable via `npm test`:
|
|
91
|
+
- `smoke.test.mjs`: source-of-truth tool inventory (26 tools by name).
|
|
92
|
+
- `stdio-transport.test.mjs`: JSON-RPC round trip via spawned stdio process.
|
|
93
|
+
- `http-transport.test.mjs`: HTTP transport end-to-end, bearer auth happy/sad paths.
|
|
94
|
+
- **README auto-regenerator** at `scripts/regen-readme-tools-table.mjs`. `npm run regen:readme` keeps the README table in sync with `ALL_TOOLS`. `npm run regen:readme -- --check` fails when stale (suitable for CI).
|
|
95
|
+
|
|
96
|
+
### Fixed
|
|
97
|
+
- **README tool table.** Previous count was 19; actual is 26. Auto-generated now.
|
|
98
|
+
- **Header comment in `index.js`.** Previously said 21; updated to 26.
|
|
99
|
+
- **`purple_ai_query`** removed from the documentation. The tool itself was removed 2026-05-03 because the underlying API requires a browser-session `teamToken` that service-account API tokens never obtain. The README, `index.js`, and `docs/mcp-tools.md` no longer reference it.
|
|
100
|
+
- **`uam_set_status` documentation.** Doc previously said valid status values include `CLOSED`. The source enum is `NEW`, `IN_PROGRESS`, `RESOLVED`; doc now matches.
|
|
101
|
+
|
|
102
|
+
### Changed
|
|
103
|
+
- **Refactored** dispatch out of `index.js` into `lib/server-core.js` so both transports use one code path. `lib/stdio-transport.js` is the extracted stdio loop; `lib/http-transport.js` is new.
|
|
104
|
+
- **package.json**:
|
|
105
|
+
- `version` 1.0.0 → 1.1.0
|
|
106
|
+
- new scripts: `start:http`, `test`, `regen:readme`
|
|
107
|
+
- new files included in the npm tarball: `deploy/`, `scripts/`, `CHANGELOG.md`
|
|
108
|
+
|
|
109
|
+
### Compatibility
|
|
110
|
+
- Default invocation is unchanged: `npx -y @pmoses-s1/s1-secops-mcp` still produces a stdio MCP server with identical behaviour to 1.0.0.
|
|
111
|
+
- Existing `claude_desktop_config.json` and `.mcp.json` configs work without modification.
|
|
112
|
+
- The 26 tools, 2 resources, and 2 prompts are unchanged from the late-1.0.0 line; only the documentation now matches reality.
|
|
113
|
+
|
|
114
|
+
## 1.0.0 - 2026-05-07
|
|
115
|
+
|
|
116
|
+
Initial public release.
|
|
117
|
+
- 19 tools across PowerQuery, S1 Mgmt REST, UAM, SDL API, Hyperautomation.
|
|
118
|
+
- stdio transport only.
|
|
119
|
+
- Credentials via env vars or auto-discovered `credentials.json`.
|
package/README.md
ADDED
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
# SentinelOne MCP Server
|
|
2
|
+
|
|
3
|
+
Model Context Protocol server orchestrating the SentinelOne Management Console, Singularity Data Lake, UAM Alert Interface, and Hyperautomation APIs. Pure Node.js 18+, zero external dependencies. Supports both stdio (for Claude Desktop / Cowork / Claude Code) and Streamable HTTP (for team-shared VM deployments) transports.
|
|
4
|
+
|
|
5
|
+
- **Single-user, local:** install with `npx`, plug into Claude Desktop in 30 seconds.
|
|
6
|
+
- **Team, VM-hosted:** install on one Linux box, per-user bearer tokens, audit logs, SIGHUP-reloadable rotation.
|
|
7
|
+
|
|
8
|
+
See **[deploy/README.md](./deploy/README.md)** for the full deployment walkthrough across all three topologies.
|
|
9
|
+
|
|
10
|
+
## What this exposes
|
|
11
|
+
|
|
12
|
+
<!-- BEGIN AUTO-GENERATED TOOLS TABLE -->
|
|
13
|
+
**26 tools** across PowerQuery, Mgmt Console, SDL API, Hyperautomation, and UAM Ingest:
|
|
14
|
+
|
|
15
|
+
| Group | Tool | Skill |
|
|
16
|
+
|-------|------|-------|
|
|
17
|
+
| PowerQuery | `powerquery_enumerate_sources` | powerquery |
|
|
18
|
+
| PowerQuery | `powerquery_run` | powerquery |
|
|
19
|
+
| PowerQuery | `powerquery_schema_discover` | powerquery |
|
|
20
|
+
| Mgmt Console | `purple_ai_alert_summary` | mgmt-console-api |
|
|
21
|
+
| Mgmt Console | `s1_api_delete` | mgmt-console-api |
|
|
22
|
+
| Mgmt Console | `s1_api_get` | mgmt-console-api |
|
|
23
|
+
| Mgmt Console | `s1_api_patch` | mgmt-console-api |
|
|
24
|
+
| Mgmt Console | `s1_api_post` | mgmt-console-api |
|
|
25
|
+
| Mgmt Console | `s1_api_put` | mgmt-console-api |
|
|
26
|
+
| Mgmt Console | `uam_add_note` | mgmt-console-api |
|
|
27
|
+
| Mgmt Console | `uam_get_alert` | mgmt-console-api |
|
|
28
|
+
| Mgmt Console | `uam_list_alerts` | mgmt-console-api |
|
|
29
|
+
| Mgmt Console | `uam_set_status` | mgmt-console-api |
|
|
30
|
+
| SDL API | `hec_ingest` | sdl-api / sdl-log-parser |
|
|
31
|
+
| SDL API | `sdl_delete_file` | sdl-api |
|
|
32
|
+
| SDL API | `sdl_get_file` | sdl-api / sdl-dashboard / sdl-log-parser |
|
|
33
|
+
| SDL API | `sdl_list_files` | sdl-api / sdl-dashboard / sdl-log-parser |
|
|
34
|
+
| SDL API | `sdl_put_file` | sdl-api / sdl-dashboard / sdl-log-parser |
|
|
35
|
+
| Hyperautomation | `ha_delete_workflow` | hyperautomation |
|
|
36
|
+
| Hyperautomation | `ha_export_workflow` | hyperautomation |
|
|
37
|
+
| Hyperautomation | `ha_get_workflow` | hyperautomation |
|
|
38
|
+
| Hyperautomation | `ha_import_workflow` | hyperautomation |
|
|
39
|
+
| Hyperautomation | `ha_list_workflows` | hyperautomation |
|
|
40
|
+
| UAM Ingest | `uam_ingest_alert` | mgmt-console-api (UAM Alert Interface) |
|
|
41
|
+
| UAM Ingest | `uam_post_alert` | mgmt-console-api (UAM Alert Interface) |
|
|
42
|
+
| UAM Ingest | `uam_post_indicators` | mgmt-console-api (UAM Alert Interface) |
|
|
43
|
+
<!-- END AUTO-GENERATED TOOLS TABLE -->
|
|
44
|
+
|
|
45
|
+
**2 resources:**
|
|
46
|
+
- `sentinelone://soc-context`: `CLAUDE.md`, the Principal SOC Analyst operating instructions.
|
|
47
|
+
- `sentinelone://credentials-status`: which credentials are configured and which API surfaces are available.
|
|
48
|
+
|
|
49
|
+
**2 prompts:**
|
|
50
|
+
- `soc_analyst`: embeds `CLAUDE.md` as a system prompt; call at session start.
|
|
51
|
+
- `session_init`: structured init: enumerate sources + triage alerts in parallel.
|
|
52
|
+
|
|
53
|
+
## Quick install
|
|
54
|
+
|
|
55
|
+
For the end-user install paths (Docker quick start, npx/uvx, and team VM), see the canonical **[README Installation section](../README.md#installation)**; credential keys and where to get them are in **[docs/credentials.md](../docs/credentials.md)**. This section is the MCP-server-specific reference: the exact npm package and pin, the reproducible install script, and the Claude Desktop stdio bridge for a shared team VM. Three paths, pick the one that matches your setup:
|
|
56
|
+
|
|
57
|
+
### A. Local single-user via `npx` (Claude Desktop / Claude Code / Cowork)
|
|
58
|
+
|
|
59
|
+
MCP runs as a subprocess on your machine, talking SentinelOne APIs directly. Credentials live in the Claude config `env` block.
|
|
60
|
+
|
|
61
|
+
Add this to `claude_desktop_config.json` (or `.mcp.json` for Claude Code):
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"s1-secops-mcp": {
|
|
67
|
+
"command": "npx",
|
|
68
|
+
"args": ["-y", "@pmoses-s1/s1-secops-mcp@1.3.0"],
|
|
69
|
+
"env": {
|
|
70
|
+
"S1_CONSOLE_URL": "https://usea1-yourorg.sentinelone.net",
|
|
71
|
+
"S1_CONSOLE_API_TOKEN": "eyJ...",
|
|
72
|
+
"S1_HEC_INGEST_URL": "https://ingest.us1.sentinelone.net",
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Restart Claude Desktop. `npx -y` caches the package on first launch.
|
|
80
|
+
|
|
81
|
+
### B. Reproducible: install script
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
curl -fsSL https://raw.githubusercontent.com/pmoses-s1/claude-skills/main/s1-secops-mcp/deploy/install.sh | bash
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Sets up a per-user npm prefix if needed, installs the package, drops a credentials skeleton at `~/.config/sentinelone/credentials.json` (mode 0600), and prints the wiring instructions for Claude Desktop.
|
|
88
|
+
|
|
89
|
+
For VM deployments, the same script in `--server` mode does everything (system user, systemd unit, initial bearer token, service start). See [deploy/README.md](./deploy/README.md).
|
|
90
|
+
|
|
91
|
+
### C. Claude Desktop connecting to a team VM (stdio bridge)
|
|
92
|
+
|
|
93
|
+
When the MCP is running as a shared service on a Linux VM (deploy topology C in [deploy/README.md](./deploy/README.md)) and you're connecting from Claude Desktop, you need a small stdio↔HTTPS shim because Claude Desktop's stable build doesn't accept `type: "http"` configs. (Claude Cowork and Claude Code do; see "[Calling the HTTP endpoint directly](#calling-the-http-endpoint-directly)" for the native `type: "http"` form.)
|
|
94
|
+
|
|
95
|
+
The bridge is a 40-line zero-dependency Node script shipped at [`deploy/bridge/s1-secops-mcp-bridge.mjs`](./deploy/bridge/s1-secops-mcp-bridge.mjs).
|
|
96
|
+
|
|
97
|
+
Each team member installs the script once:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
mkdir -p ~/.local/bin
|
|
101
|
+
curl -fsSL https://raw.githubusercontent.com/pmoses-s1/claude-skills/main/s1-secops-mcp/deploy/bridge/s1-secops-mcp-bridge.mjs \
|
|
102
|
+
-o ~/.local/bin/s1-secops-mcp-bridge.mjs
|
|
103
|
+
chmod +x ~/.local/bin/s1-secops-mcp-bridge.mjs
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Then adds this block to `claude_desktop_config.json`:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"mcpServers": {
|
|
111
|
+
"s1-secops-mcp": {
|
|
112
|
+
"command": "node",
|
|
113
|
+
"args": ["/Users/<you>/.local/bin/s1-secops-mcp-bridge.mjs"],
|
|
114
|
+
"env": {
|
|
115
|
+
"MCP_URL": "https://mcp.example.internal:8764/mcp",
|
|
116
|
+
"MCP_BEARER": "<your personal bearer token>"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Cmd+Q and reopen Claude Desktop. SentinelOne credentials live on the VM in `/etc/s1-secops-mcp/credentials.json`, only the bearer token sits in each user's local Claude config. Full setup + smoke-test instructions at [`deploy/bridge/README.md`](./deploy/bridge/README.md).
|
|
124
|
+
|
|
125
|
+
## Credentials
|
|
126
|
+
|
|
127
|
+
Credential keys, where to get each one, and the two token types are documented canonically in **[docs/credentials.md](../docs/credentials.md)**. This section adds the MCP-server-specific detail: which tools each key gates, and the server's full credential-resolution order.
|
|
128
|
+
|
|
129
|
+
`S1_CONSOLE_URL` and `S1_CONSOLE_API_TOKEN` are sufficient for the PowerQuery, Mgmt Console REST, Purple AI summary, and UAM tools (16 of the 26).
|
|
130
|
+
|
|
131
|
+
`S1_HEC_INGEST_URL` is **required** for the three UAM Ingest tools (`uam_ingest_alert`, `uam_post_indicators`, `uam_post_alert`) and for `hec_ingest`. Without it those tools error at call time; the rest still work.
|
|
132
|
+
|
|
133
|
+
`SDL_*` keys gate the SDL tools as follows:
|
|
134
|
+
|
|
135
|
+
| Variable | Description | Required for |
|
|
136
|
+
|----------|-------------|--------------|
|
|
137
|
+
| `S1_CONSOLE_URL` | Console URL, e.g. `https://usea1-acme.sentinelone.net` | All Mgmt + PowerQuery tools |
|
|
138
|
+
| `S1_CONSOLE_API_TOKEN` | Mgmt Console API token (Settings → Users → Service Users) | All Mgmt + PowerQuery + UAM tools |
|
|
139
|
+
| `S1_HEC_INGEST_URL` | HEC ingest host, e.g. `https://ingest.us1.sentinelone.net` | `uam_ingest_alert`, `uam_post_indicators`, `uam_post_alert`, `hec_ingest` |
|
|
140
|
+
|
|
141
|
+
### Credential resolution order (highest priority wins)
|
|
142
|
+
|
|
143
|
+
1. Environment variables (set in `claude_desktop_config.json` `env`, systemd `EnvironmentFile`, or your shell).
|
|
144
|
+
2. `S1_CREDS_FILE`: explicit path to a JSON file (recommended for VM deployments and secret-store integrations).
|
|
145
|
+
3. `COWORK_WORKSPACE/credentials.json`.
|
|
146
|
+
4. Walk-up from the current working directory looking for `credentials.json`.
|
|
147
|
+
5. `~/mnt/<folder>/credentials.json` (Cowork workspace mounts).
|
|
148
|
+
6. `$CLAUDE_CONFIG_DIR/sentinelone/credentials.json`.
|
|
149
|
+
7. `~/.config/sentinelone/credentials.json`.
|
|
150
|
+
|
|
151
|
+
The server logs the resolved credential source at startup so you can diagnose surprise overrides.
|
|
152
|
+
|
|
153
|
+
## Transport modes
|
|
154
|
+
|
|
155
|
+
### stdio (default)
|
|
156
|
+
|
|
157
|
+
The transport used by Claude Desktop, Claude Code, Claude Cowork, and any other client launched via `npx` / `node index.js`.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
s1-secops-mcp # auto-discovers credentials
|
|
161
|
+
node index.js # same as above, from a local clone
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Streamable HTTP
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
s1-secops-mcp --transport http # 127.0.0.1:8765/mcp, no auth
|
|
168
|
+
s1-secops-mcp --transport http --host 0.0.0.0 # all interfaces, no auth (loud warning)
|
|
169
|
+
MCP_BEARER_TOKENS_FILE=/etc/s1-secops-mcp/bearer-tokens.json \
|
|
170
|
+
s1-secops-mcp --transport http --host 0.0.0.0 # team mode with per-user tokens
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Configuration via flags or environment variables:
|
|
174
|
+
|
|
175
|
+
| Flag | Env var | Default | Purpose |
|
|
176
|
+
|------|---------|---------|---------|
|
|
177
|
+
| `--transport` | `MCP_TRANSPORT` | `stdio` | `stdio` or `http`. |
|
|
178
|
+
| `--host` | `MCP_HTTP_HOST` | `127.0.0.1` | HTTP bind address. Use `0.0.0.0` for cross-host access. |
|
|
179
|
+
| `--port` | `MCP_HTTP_PORT` | `8765` | HTTP port. |
|
|
180
|
+
| `--path` | `MCP_HTTP_PATH` | `/mcp` | MCP endpoint path. |
|
|
181
|
+
|
|
182
|
+
In HTTP mode the server exposes:
|
|
183
|
+
|
|
184
|
+
- `POST /mcp`: accepts JSON-RPC, returns JSON-RPC. The MCP entry point.
|
|
185
|
+
- `GET /healthz`: returns `200 ok`. For load balancer probes; no auth.
|
|
186
|
+
|
|
187
|
+
### Team auth: bearer tokens
|
|
188
|
+
|
|
189
|
+
To enable team auth, set one of:
|
|
190
|
+
|
|
191
|
+
- `MCP_BEARER_TOKENS_FILE=/path/to/file.json` (recommended). The file is `{ "<name>": "<token>", ... }`. Names appear in audit logs; revoking a user is a one-line edit. SIGHUP reloads without restart.
|
|
192
|
+
- `MCP_BEARER_TOKENS="token1,token2,..."` (fallback, no per-user names).
|
|
193
|
+
|
|
194
|
+
Token rotation:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
sudo vim /etc/s1-secops-mcp/bearer-tokens.json # add/remove entries
|
|
198
|
+
sudo systemctl reload s1-secops-mcp # SIGHUP, no connection drops
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
If neither env var is set, HTTP transport runs **without** authentication and the server logs a warning at startup. That's acceptable for `--host 127.0.0.1` single-user use; never use it on `0.0.0.0` in production.
|
|
202
|
+
|
|
203
|
+
### Audit log
|
|
204
|
+
|
|
205
|
+
Every authenticated HTTP request emits a structured stderr line that systemd captures via journald:
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
[audit] 2026-05-28T15:01:22.413Z | alice | tools/call | name=powerquery_run | 200 ok
|
|
209
|
+
[audit] 2026-05-28T15:01:34.221Z | bob | tools/list | - | 200 ok
|
|
210
|
+
[audit] 2026-05-28T17:03:11.221Z | - | - | - | 401 unauthorized
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Calling the HTTP endpoint directly
|
|
214
|
+
|
|
215
|
+
You don't need an MCP client library. The HTTP transport is plain JSON-RPC 2.0 over `POST`, with bearer auth in the `Authorization` header. Any HTTP client works, `curl`, Python `requests`, Node `fetch`, Go `net/http`, etc. This is how you'd integrate from a custom script, a CI job, or a non-MCP tool that just needs to call SentinelOne via the same wrapped surface.
|
|
216
|
+
|
|
217
|
+
> **Ready-made check:** [`scripts/smoke-test-http.sh`](./scripts/smoke-test-http.sh) runs the six contract checks documented below (healthz, initialize, tools/list, tools/call, bad-bearer 401, unknown-method JSON-RPC error) and prints PASS/FAIL. Run as `MCP_HOST=<host:port> MCP_BEARER=<token> bash s1-secops-mcp/scripts/smoke-test-http.sh`. Good for new-team-member onboarding and post-rotation validation.
|
|
218
|
+
|
|
219
|
+
### Endpoint contract
|
|
220
|
+
|
|
221
|
+
| Item | Value |
|
|
222
|
+
|---|---|
|
|
223
|
+
| Method | `POST` |
|
|
224
|
+
| URL | `https://<host>:<port>/mcp` (path is `/mcp` by default; configurable with `--path`) |
|
|
225
|
+
| `Content-Type` | `application/json` |
|
|
226
|
+
| `Authorization` | `Bearer <token>` (one of the tokens in `MCP_BEARER_TOKENS_FILE`) |
|
|
227
|
+
| Body | JSON-RPC 2.0 envelope |
|
|
228
|
+
| Response | JSON-RPC 2.0 envelope (`result` on success, `error` on failure) |
|
|
229
|
+
|
|
230
|
+
Health probe (no auth, no JSON): `GET /healthz` returns `200 ok`.
|
|
231
|
+
|
|
232
|
+
### Initialize, then list tools, then call one (curl)
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
HOST=mcp.s1.internal
|
|
236
|
+
TOKEN='your-bearer-token-here'
|
|
237
|
+
|
|
238
|
+
# 1. initialize (required first call per spec; advertises protocol version and capabilities)
|
|
239
|
+
curl -s -X POST "https://$HOST/mcp" \
|
|
240
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
241
|
+
-H "Content-Type: application/json" \
|
|
242
|
+
-d '{
|
|
243
|
+
"jsonrpc": "2.0",
|
|
244
|
+
"id": 1,
|
|
245
|
+
"method": "initialize",
|
|
246
|
+
"params": {
|
|
247
|
+
"protocolVersion": "2024-11-05",
|
|
248
|
+
"capabilities": {},
|
|
249
|
+
"clientInfo": { "name": "my-script", "version": "1.0" }
|
|
250
|
+
}
|
|
251
|
+
}'
|
|
252
|
+
# -> {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{...},"serverInfo":{...}}}
|
|
253
|
+
|
|
254
|
+
# 2. list every tool the server exposes
|
|
255
|
+
curl -s -X POST "https://$HOST/mcp" \
|
|
256
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
257
|
+
-H "Content-Type: application/json" \
|
|
258
|
+
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
|
|
259
|
+
| jq '.result.tools | length'
|
|
260
|
+
# -> 26
|
|
261
|
+
|
|
262
|
+
# 3. call a tool (here: list custom detection rules with the mandatory isLegacy=false)
|
|
263
|
+
curl -s -X POST "https://$HOST/mcp" \
|
|
264
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
265
|
+
-H "Content-Type: application/json" \
|
|
266
|
+
-d '{
|
|
267
|
+
"jsonrpc": "2.0",
|
|
268
|
+
"id": 3,
|
|
269
|
+
"method": "tools/call",
|
|
270
|
+
"params": {
|
|
271
|
+
"name": "s1_api_get",
|
|
272
|
+
"arguments": {
|
|
273
|
+
"path": "/web/api/v2.1/cloud-detection/rules",
|
|
274
|
+
"params": { "isLegacy": false, "limit": 50 }
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}' \
|
|
278
|
+
| jq '.result.content[0].text | fromjson | .pagination.totalItems'
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Python (requests)
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
import json
|
|
285
|
+
import requests
|
|
286
|
+
|
|
287
|
+
URL = "https://mcp.s1.internal/mcp"
|
|
288
|
+
TOKEN = "your-bearer-token-here"
|
|
289
|
+
HEADERS = {
|
|
290
|
+
"Authorization": f"Bearer {TOKEN}",
|
|
291
|
+
"Content-Type": "application/json",
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
def rpc(method, params=None, id=1):
|
|
295
|
+
body = {"jsonrpc": "2.0", "id": id, "method": method}
|
|
296
|
+
if params is not None:
|
|
297
|
+
body["params"] = params
|
|
298
|
+
r = requests.post(URL, headers=HEADERS, json=body, timeout=30)
|
|
299
|
+
r.raise_for_status()
|
|
300
|
+
return r.json()
|
|
301
|
+
|
|
302
|
+
# initialize once per session
|
|
303
|
+
rpc("initialize", {
|
|
304
|
+
"protocolVersion": "2024-11-05",
|
|
305
|
+
"capabilities": {},
|
|
306
|
+
"clientInfo": {"name": "python-client", "version": "1.0"},
|
|
307
|
+
}, id=0)
|
|
308
|
+
|
|
309
|
+
# list tools
|
|
310
|
+
tools = rpc("tools/list", id=1)["result"]["tools"]
|
|
311
|
+
print(f"{len(tools)} tools available")
|
|
312
|
+
|
|
313
|
+
# call a tool
|
|
314
|
+
resp = rpc("tools/call", {
|
|
315
|
+
"name": "powerquery_run",
|
|
316
|
+
"arguments": {
|
|
317
|
+
"query": "dataSource.name=* | group count=count() by dataSource.name | sort -count | limit 10",
|
|
318
|
+
"hours": 24,
|
|
319
|
+
},
|
|
320
|
+
}, id=2)
|
|
321
|
+
|
|
322
|
+
# Tool results live in result.content[0].text as a JSON string.
|
|
323
|
+
payload = json.loads(resp["result"]["content"][0]["text"])
|
|
324
|
+
print(json.dumps(payload, indent=2))
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Node (built-in fetch, Node 18+)
|
|
328
|
+
|
|
329
|
+
```javascript
|
|
330
|
+
const URL = 'https://mcp.s1.internal/mcp';
|
|
331
|
+
const TOKEN = process.env.MCP_BEARER;
|
|
332
|
+
|
|
333
|
+
async function rpc(method, params, id = 1) {
|
|
334
|
+
const body = { jsonrpc: '2.0', id, method };
|
|
335
|
+
if (params !== undefined) body.params = params;
|
|
336
|
+
const res = await fetch(URL, {
|
|
337
|
+
method: 'POST',
|
|
338
|
+
headers: {
|
|
339
|
+
'Authorization': `Bearer ${TOKEN}`,
|
|
340
|
+
'Content-Type': 'application/json',
|
|
341
|
+
},
|
|
342
|
+
body: JSON.stringify(body),
|
|
343
|
+
});
|
|
344
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
|
|
345
|
+
return res.json();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
await rpc('initialize', {
|
|
349
|
+
protocolVersion: '2024-11-05',
|
|
350
|
+
capabilities: {},
|
|
351
|
+
clientInfo: { name: 'node-client', version: '1.0' },
|
|
352
|
+
}, 0);
|
|
353
|
+
|
|
354
|
+
const { result } = await rpc('tools/list', null, 1);
|
|
355
|
+
console.log(`${result.tools.length} tools available`);
|
|
356
|
+
|
|
357
|
+
const call = await rpc('tools/call', {
|
|
358
|
+
name: 'uam_list_alerts',
|
|
359
|
+
arguments: { first: 20, status: 'NEW' },
|
|
360
|
+
}, 2);
|
|
361
|
+
console.log(JSON.parse(call.result.content[0].text));
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### JSON-RPC envelope shapes
|
|
365
|
+
|
|
366
|
+
**Success response:**
|
|
367
|
+
|
|
368
|
+
```json
|
|
369
|
+
{
|
|
370
|
+
"jsonrpc": "2.0",
|
|
371
|
+
"id": 2,
|
|
372
|
+
"result": { ... method-specific payload ... }
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Error response:**
|
|
377
|
+
|
|
378
|
+
```json
|
|
379
|
+
{
|
|
380
|
+
"jsonrpc": "2.0",
|
|
381
|
+
"id": 2,
|
|
382
|
+
"error": {
|
|
383
|
+
"code": -32602,
|
|
384
|
+
"message": "Tool not found: bad_tool_name"
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**Notifications** (one-way messages with no `id`, e.g. `notifications/initialized`):
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
curl -i -s -X POST "https://$HOST/mcp" \
|
|
393
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
394
|
+
-H "Content-Type: application/json" \
|
|
395
|
+
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
|
|
396
|
+
# -> HTTP/2 202 (no body, per JSON-RPC spec)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Error codes you'll actually see
|
|
400
|
+
|
|
401
|
+
| HTTP | JSON-RPC code | Meaning |
|
|
402
|
+
|---|---|---|
|
|
403
|
+
| 200 | (none, has `result`) | Success |
|
|
404
|
+
| 200 | `-32601` | Method not found (e.g. typo in method name) |
|
|
405
|
+
| 200 | `-32602` | Invalid params (tool not found, missing required arg) |
|
|
406
|
+
| 200 | `-32603` | Tool handler threw: upstream S1 API error usually |
|
|
407
|
+
| 400 | `-32700` | Parse error (malformed JSON body) |
|
|
408
|
+
| 400 | `-32600` | Invalid request (e.g. JSON-RPC batch, not supported) |
|
|
409
|
+
| 401 | `-32001` | Missing or invalid bearer token |
|
|
410
|
+
| 405 | (none) | Wrong HTTP method on `/mcp` (only POST is accepted) |
|
|
411
|
+
| 413 | `-32600` | Body exceeds 4 MB |
|
|
412
|
+
|
|
413
|
+
### Tool inputs and outputs
|
|
414
|
+
|
|
415
|
+
Every tool's input schema is documented in the `tools/list` response (look at the `inputSchema` JSON Schema on each tool). The response shape is always:
|
|
416
|
+
|
|
417
|
+
```json
|
|
418
|
+
{
|
|
419
|
+
"content": [
|
|
420
|
+
{ "type": "text", "text": "<JSON-encoded result>" }
|
|
421
|
+
],
|
|
422
|
+
"isError": false
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Parse `content[0].text` as JSON to get the actual data the tool returned. Tool-level errors set `isError: true` and put the error text in the same field.
|
|
427
|
+
|
|
428
|
+
### Row / page limits (measured, not enforced)
|
|
429
|
+
|
|
430
|
+
The `maxRows` (`powerquery_run`) and `first` (`uam_list_alerts`) parameters are soft client-side hints, not hard backend caps. Live-verified 2026-07-29:
|
|
431
|
+
|
|
432
|
+
- `powerquery_run` `maxRows`: default 1000, but not a ceiling. The LRQ engine returns as many rows as the query's own `| limit N` asks for. A `| limit 20000` query with `maxRows: 20000` returned 20,000 rows in a single response. Set `maxRows` to match a large `| limit`; the practical ceiling is LRQ response size, not a fixed 5000. Prefer aggregating in the query (`| group ... | limit N`) over pulling tens of thousands of raw rows.
|
|
433
|
+
- `uam_list_alerts` `first`: default 20, not enforced client-side. The UAM GraphQL backend accepts larger pages: `first: 500` returned 500 alerts with `pageInfo.hasNextPage: true`. Paginate with the returned `pageInfo.endCursor` via `after` rather than requesting one unbounded page.
|
|
434
|
+
|
|
435
|
+
## CLI reference
|
|
436
|
+
|
|
437
|
+
```
|
|
438
|
+
s1-secops-mcp [options]
|
|
439
|
+
|
|
440
|
+
OPTIONS
|
|
441
|
+
--transport <stdio|http> Transport. Default: stdio.
|
|
442
|
+
--host <host> HTTP bind address. Default: 127.0.0.1.
|
|
443
|
+
--port <port> HTTP port. Default: 8765.
|
|
444
|
+
--path <path> HTTP MCP endpoint path. Default: /mcp.
|
|
445
|
+
-h, --help Show help.
|
|
446
|
+
-v, --version Show server version.
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
## Architecture
|
|
450
|
+
|
|
451
|
+
```
|
|
452
|
+
s1-secops-mcp/
|
|
453
|
+
index.js Entry: flag parsing + transport selection
|
|
454
|
+
lib/
|
|
455
|
+
server-core.js Tool registry, JSON-RPC dispatch (transport-agnostic)
|
|
456
|
+
stdio-transport.js stdin/stdout JSON-RPC loop
|
|
457
|
+
http-transport.js Streamable HTTP (node:http, zero deps)
|
|
458
|
+
auth.js Bearer token allowlist with SIGHUP reload
|
|
459
|
+
credentials.js S1 + SDL credential resolution
|
|
460
|
+
s1.js Mgmt REST + LRQ PowerQuery + Purple AI + UAM GraphQL
|
|
461
|
+
sdl.js SDL config files + V1 query
|
|
462
|
+
uam-ingest.js HEC alert/indicator ingestion
|
|
463
|
+
tools/
|
|
464
|
+
powerquery.js PowerQuery enumerate/run/schema-discover
|
|
465
|
+
mgmt-console.js S1 REST verbs + Purple AI summary + UAM
|
|
466
|
+
sdl-api.js SDL config file + log ingestion tools
|
|
467
|
+
hyperautomation.js Hyperautomation list/get/import/export/delete
|
|
468
|
+
uam-ingest.js UAM Alert Interface ingestion tools
|
|
469
|
+
deploy/
|
|
470
|
+
install.sh One-shot installer (Mac and Linux)
|
|
471
|
+
systemd/ Service unit for Linux VM deployments
|
|
472
|
+
caddy/ TLS reverse proxy template
|
|
473
|
+
README.md Deployment walkthrough
|
|
474
|
+
scripts/
|
|
475
|
+
regen-readme-tools-table.mjs Tools-table regenerator (no drift)
|
|
476
|
+
tests/ Smoke + stdio + HTTP test suites (node --test)
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
## Auth patterns (implemented)
|
|
480
|
+
|
|
481
|
+
| API surface | Auth header | Key |
|
|
482
|
+
|-------------|-------------|-----|
|
|
483
|
+
| S1 Mgmt REST API | `Authorization: ApiToken <jwt>` | `S1_CONSOLE_API_TOKEN` |
|
|
484
|
+
| LRQ PowerQuery | `Authorization: Bearer <jwt>` | Same token, different prefix |
|
|
485
|
+
| Purple AI GraphQL | `Authorization: ApiToken <jwt>` | `S1_CONSOLE_API_TOKEN` |
|
|
486
|
+
| UAM GraphQL | `Authorization: ApiToken <jwt>` | `S1_CONSOLE_API_TOKEN` |
|
|
487
|
+
| UAM HEC ingest | `Authorization: Bearer <jwt>` | `S1_CONSOLE_API_TOKEN` |
|
|
488
|
+
|
|
489
|
+
## Testing
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
npm test
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
Three test suites under `tests/`:
|
|
496
|
+
|
|
497
|
+
- `smoke.test.mjs`: introspects `ALL_TOOLS` directly, no spawning. Asserts 26 tools by name; catches any drift between code and the README regenerator.
|
|
498
|
+
- `stdio-transport.test.mjs`: spawns the server in stdio mode, exercises `initialize`, `tools/list`, `resources/list`, `prompts/list`, and error handling.
|
|
499
|
+
- `http-transport.test.mjs`: spawns in HTTP mode on a random ephemeral port, exercises `/healthz`, `POST /mcp`, both auth-required and auth-optional flows, and the env-var token fallback.
|
|
500
|
+
|
|
501
|
+
The smoke suite is the source of truth for the tool count and is what `scripts/regen-readme-tools-table.mjs` derives the README table from. If the table goes stale, `npm run regen:readme -- --check` fails CI; `npm run regen:readme` fixes it.
|
|
502
|
+
|
|
503
|
+
## Updating CLAUDE.md
|
|
504
|
+
|
|
505
|
+
The `sentinelone://soc-context` resource and `soc_analyst` prompt load `CLAUDE.md` at server startup. Resolution order:
|
|
506
|
+
|
|
507
|
+
1. `S1_CLAUDE_MD_PATH` env var (explicit absolute path).
|
|
508
|
+
2. `<cwd>/CLAUDE.md`: your Cowork project folder, when launched from there.
|
|
509
|
+
3. Same-dir / parent / grandparent of the server's `index.js`: when running from a git clone.
|
|
510
|
+
|
|
511
|
+
For npx installs without a CLAUDE.md nearby, set `S1_CLAUDE_MD_PATH` in the `env` block of `claude_desktop_config.json` to point at the one in your Cowork project folder. Restart Claude Desktop to pick up edits.
|
|
512
|
+
|
|
513
|
+
## Removed tools
|
|
514
|
+
|
|
515
|
+
`purple_ai_query` and `purple_ai_investigate` were removed on 2026-05-03. Both required a browser-session `teamToken` from `/sdl/v2/graphql` that service-account API tokens never obtain (returns `AsimovError` / `SERVICE_ERROR`). Use `mcp__purple-mcp__purple_ai` instead, which holds the right credentials.
|
|
516
|
+
|
|
517
|
+
## Version history
|
|
518
|
+
|
|
519
|
+
See [CHANGELOG.md](./CHANGELOG.md).
|