Skill v1.0.0
currentLLM-judged scan90/100version: "1.0.0" name: mgmt-console-api author: Prithvi Moses <prithvi.moses@sentinelone.com> description: >- Use whenever the user wants to query, update, create, or act on a SentinelOne Management Console: threats, alerts, agents, sites, accounts, groups, exclusions, RemoteOps, Deep Visibility, Hyperautomation, Unified Alert Management (UAM), Purple AI, IOCs, or any other S1 Mgmt API resource. Trigger on "console", "query/update/create console", "SentinelOne", "S1", "Singularity", "UAM", "Purple AI", "/web/api/v2.1/...", S1 agent/threat/site IDs, or asks like "list endpoints", "triage alerts", "add note to alert", "create an IOC", "isolate endpoint", "run RemoteOps", "pull DV results". For alerts the PRIMARY API is GraphQL UAM at /web/api/v2.1/unifiedalerts/graphql; REST /cloud-detection/alerts is SECONDARY (older, int64 IDs). Defer to Purple MCP if the user says "purple mcp"; this skill is the backup then. Covers the S1 Mgmt REST API (781 ops, v2.1), UAM GraphQL and Purple AI GraphQL through the s1-secops-mcp tools (s1_api_, uam_), plus a host-only Python client, endpoint index, and reversible tests.
SentinelOne Management Console API
Covers the SentinelOne Management Console API (Swagger 2.0, spec version 2.1, 781 operations) with a compact endpoint index, per-tag reference files, and the s1-secops-mcp MCP tools that make the calls.
Primary path: the `s1-secops-mcp` MCP tools. Every console call in this skill runs through them:s1_api_get/s1_api_post/s1_api_put/s1_api_patch/s1_api_deletefor REST,s1_api_downloadfor binary responses (RemoteOps fetch-files, threat file fetch, exports),uam_list_alerts/uam_get_alert/uam_add_note/uam_set_status/uam_set_verdict/uam_assign_alert/uam_available_actions/uam_post_alertfor alerts,purple_ai_alert_summary, andpowerquery_run/powerquery_schema_discoverfor logs. The server runs on the user's machine, so it reaches*.sentinelone.netwhere the Cowork sandbox cannot; it reads credentials from environment variables or the OS keychain and masks tokens in all output. If the tools are missing, the user has not connected the server: point them to the s1-secops-mcp README (s1-secops-mcp/README.mdin the s1-secops-skills source repo,mcp/s1-secops-mcp/README.mdin ai-siem; not shipped inside the plugin). For natural-language Purple AI queries and AI investigations, use the Purple MCP (mcp__purple-mcp__purple_ai) directly; those operations require a browser-session teamToken that API tokens never obtain.The Python clients under `scripts/` are host-only.S1Client,call_endpoint.py,pq.py,inspect_source.py,unified_alerts.pyanduam_alert_interface.pywork from Claude Code or a terminal on the user's machine, where they read the same environment variables or keychain entries as the MCP server. They cannot reach the tenant from the Cowork sandbox, so do not try them there and do not treat a proxy error as an empty result.Large results: passoutputFile(an absolute path on the user's machine insideS1_OUTPUT_DIRS, default home and temp; under the Docker launcher, a path inside theS1_OUTPUT_DIRfolder it mounted) tos1_api_get,s1_api_downloadorpowerquery_run. The full result is written there and only a summary comes back, so bulk data does not pass through the context window. If the server refuses the path because no folder is shared with the container, tell the user to run~/.local/bin/s1-secops-mcp-launch.sh install --output-dir <folder>(Windows:-OutputDir) and restart Claude Desktop; do not ask them to setS1_OUTPUT_DIRS, which never reaches the container.Load this skill BEFORE any console/UAM *write*, not just reads. Add-note, set-analyst-verdict, set-status, assign, and IOC-create all run through the UAMalertTriggerActionsmutation with a specific action-id +OrFilterSelectionInputshape (seereferences/UNIFIED_ALERTS.md): action ids areS1/alert/addNote/analystVerdictUpdate/statusUpdate/assignUser(payload{assignUser:{value:"<numeric user id>"}},value:nullunassigns;userEmailis a ValidationError), and in an MCP clientuam_set_status/uam_set_verdict/uam_assign_alertsend the console's exact request and verify by re-reading; the alert is targeted byfilter:{or:[{and:[{fieldId:"id",stringEqual:{value:<id>}}]}]}, the alert id is the FILTER, never the actionid. Hand-rolling this GraphQL from live schema introspection is the fast path to a wrong action-id/filter and a misleading empty result. Also:alertTriggerActionsreturns a success-shapedActionsTriggeredeven when nothing changed, so pass the payload throughaction_outcome()(it reduces the union toappliedplus the per-alert reasons) and re-query (uam_get_alert) to confirm the write landed. The refusal reason is inactions[].failure[].errorMessage, which is whytrigger_actionsselects that field. Before stating why an action failed, callavailable_actions(...)with a narrow filter and readisDisabled/disabledReason;trigger_actionsdoes this on failure (diagnose_failures=True) andexplain_action_failurereportsnot_offered/disabled/offered. Availability is filtered by the caller's permissions AND the alert type (one token can be offeredstatusUpdateon a native STAR alert and not on one ingested via/v1/alerts, while a console user session performs either), and is scope-sensitive (S1/incident/*isINCIDENT_ACTIONS_ONLY_AVAILABLE_FROM_SITE_VIEWunder ACCOUNT, enabled under SITE). Sonot_offeredmeans this identity lacks what this alert needs: check the service user's UAM permissions, and seehyperautomation/references/integration-catalog.mdfor native write-back actions.
Setup: credentials live in the OS keychain or the environment
There is no credentials file. Each value resolves per name: environment variables first, then the OS keychain (service sentinelone-mcp, account <profile>:<NAME>, profile from S1_PROFILE, default default). The user stores the values once on their own machine:
s1-secops-mcp setup # prompts without echo, stores in the keychain, verifies read-backs1-secops-mcp status # shows where each value comes from, maskeds1-secops-mcp forget --name S1_CONSOLE_API_TOKEN # remove one value
| Name | What it is | |
|---|---|---|
S1_CONSOLE_URL | Tenant console URL (no trailing slash, no /web/api/v2.1). | |
S1_CONSOLE_API_TOKEN | API token from Settings → Users → Service Users. Authorises REST, UAM GraphQL, LRQ and SDL config calls. | |
S1_HEC_INGEST_URL | Regional ingest host (see SentinelOne Endpoint URLs by Region). Needed only to push alerts into UAM (uam_post_alert) or ingest logs. | |
S1_HEC_TOKEN | SDL Log Write Key, used only for raw log ingest (hec_ingest, see the sdl-api skill). UAM alert ingest uses the console token instead. | |
S1_SCOPE | Optional default S1-Scope, <accountId> or <accountId>:<siteId>. |
IOC writes (/threat-intelligence/iocs) refuse a token whose user spans several accounts (HTTP 403, code 4030010). The user needs a console API token minted at a single account or site, stored in its own keychain profile (s1-secops-mcp setup --profile <name>) and served by a second MCP entry with S1_PROFILE=<name> (or made the default token).
If a tool reports a missing credential, stop and ask the user to run s1-secops-mcp setup on their machine. Never ask the user to paste a token into the chat, and never write a token to a file.
Workflow
When the user asks for something involving the S1 API, follow this pattern:
- Find the right endpoint.
- If the user's ask is verb-shaped ("list / count / isolate / hunt …") and you need orientation, read
references/CAPABILITY_MAP.mdfirst; it's a compact per-tag summary of what verbs each resource supports. - For a specific multi-step task ("threat triage", "endpoint isolation", "DV hunt"),
references/WORKFLOWS.mdhas ready-to-adapt recipes. - Otherwise go straight to
scripts/search_endpoints.pywith a keyword matching the user's intent. It now ranks results by relevance (path segment hits + verb intent + tag) and supports synonyms ("isolate" → "disconnect", "endpoint" → "agent"). Add--only-worksto restrict to endpoints confirmed reachable on this tenant by the most recent smoke test.
- Read the per-tag reference. Open
references/tags/<Tag>.md(names match the table inreferences/TAG_INDEX.md) to see full parameter lists, descriptions, required permissions, and response codes for that group. Only read the tag file(s) relevant to the task; don't read them all. - Call the endpoint. Use the MCP tools:
s1_api_getfor reads (issue independent GETs as parallel tool calls in one turn),s1_api_post/s1_api_put/s1_api_patch/s1_api_deletefor writes,s1_api_downloadfor binary responses. On the user's host (Claude Code or a terminal),scripts/call_endpoint.pyandS1Clientfromscripts/s1_client.pyremain available for loops, joins, and transforms;c.get_many([(path, params), ...])fans out in parallel over pooled connections. - Paginate correctly. S1 list endpoints use cursor-based pagination. With
s1_api_get, passlimitand followpagination.nextCursorwithcursor=<value>until it is null; for a full export, write each page to its ownoutputFile. On the host, the client'spaginate()anditer_items()handle this automatically. Avoid manualskip/limitmath, which caps at 1000 items. - Summarize the result for the user. Don't dump raw JSON unless asked. Prefer a short prose summary plus a table or CSV/XLSX if the volume warrants.
GET vs POST: the rule you must follow
The S1 API uses GET for every read operation, listing, counting, and exporting. POST is only for actions and mutations. Violating this produces HTTP 404, not a permissions error, because the path simply does not exist.
Read (always GET):
| Intent | Correct endpoint | |
|---|---|---|
| List agents | GET /web/api/v2.1/agents | |
| Count agents | GET /web/api/v2.1/agents/count → {"data":{"total":N}} | |
| List threats | GET /web/api/v2.1/threats | |
| Count threats | GET /web/api/v2.1/threats?countOnly=true or check pagination.totalItems from any list call | |
| Export threats to CSV | GET /web/api/v2.1/threats/export (no extra params required) | |
| List sites / accounts / groups | GET /web/api/v2.1/{sites,accounts,groups} | |
| Get system info | GET /web/api/v2.1/system/info |
Write / action (POST/PUT/DELETE):
- Agent actions (isolate, reconnect, uninstall, scan):
POST /web/api/v2.1/agents/actions/<action> - Threat actions (mitigate, verdict, fetch-file):
POST /web/api/v2.1/threats/<action> - Mutations (notes, exclusions, IOCs, custom rules): POST/PUT/DELETE on the relevant resource path
Paths that do not exist; never call these:
These paths return HTTP 404 and have never existed in the v2.1 spec. They are plausible-sounding guesses that are consistently wrong:
| Wrong path | Why you might guess it | Correct alternative | |
|---|---|---|---|
POST /web/api/v2.1/agents/ids | "query agents by IDs" | GET /web/api/v2.1/agents?ids=<id1>,<id2> | |
POST /web/api/v2.1/threats/summary | "get a threat summary/count" | GET /web/api/v2.1/threats?countOnly=true | |
POST /web/api/v2.1/export/threats | "export threats" | GET /web/api/v2.1/threats/export | |
POST /web/api/v2.1/threats/count | "count threats via POST" | GET /web/api/v2.1/threats?countOnly=true | |
POST /web/api/v2.1/agents/summary | "agent summary" | GET /web/api/v2.1/agents/count + GET /web/api/v2.1/agents |
If you are about to call s1_api_post for a read/count/export operation, stop and switch to s1_api_get. Before any s1_api_post call, confirm the path exists: python3 scripts/search_endpoints.py "<keyword>" (local index search, no network, so it also works in the sandbox), or grep references/endpoint_index.json. If the path does not appear, it does not exist.
Schema gotchas: confirmed on live tenant
These are fields where the obvious guess is wrong. All verified against the live tenant.
The full request-body schema gotchas for Custom Detection / STAR rules, Saved Filters, and PowerQuery Scheduled Detections (the queryType / queryLang matrix, the isLegacy=false listing trap, confirmed CREATE / PUT / DELETE bodies, and the enable / disable endpoints) live in references/detection-rules.md. Read it before composing any POST or PUT to /cloud-detection/rules or /filters.
Alert notes: UAM GraphQL
deleteAlertNote has a 30-90 second propagation window after creation before it can be deleted. The mutation uses alertNoteId, not noteId:
mutation del($nid: ID!) {deleteAlertNote(alertNoteId: $nid) { data { id } }}
If you call this immediately after creating a note you will get "Alert Note with ID ... does not have mgmt_note_id set". Retry with backoff; the unified_alerts.delete_alert_note() helper does this automatically.
Probing a new tenant
When starting on an unfamiliar tenant, run the non-destructive smoke test once from the user's host (it calls the tenant directly, so it does not run in the Cowork sandbox):
python scripts/smoke_test_queries.py --workers 12
It enumerates every GET plus a curated allow-list of read-only query POSTs, records which ones return 200/403/404/etc., and writes references/tenant_capabilities.md and .json. Useful for "what's this token actually allowed to do" and as a pre-sales capability snapshot. The sweep is read-only, no writes, no agent actions, so tenant start-state and end-state are identical.
Files in this skill
Every script below that calls the tenant is host-only (Claude Code or a terminal on the user's machine) and reads credentials from environment variables or the OS keychain, like the MCP server. In Cowork, use the MCP tool named in the Workflow section instead.
scripts/s1_client.py: importable Python client. Handles auth, pooled HTTP connections, retries on 429/5xx, pagination, parallel fan-out viaget_many(), and optional short-TTL response caching for rarely-changing reads (accounts, sites, groups, system/info, etc.).scripts/call_endpoint.py: CLI for one-shot calls:python scripts/call_endpoint.py GET /web/api/v2.1/agents --param limit=5.scripts/search_endpoints.py: ranked keyword search over the endpoint index, with synonym expansion and an--only-worksfilter that restricts to endpoints confirmed reachable on this tenant.scripts/smoke_test_queries.py: non-destructive sweep of every GET + safe query POST. Writesreferences/tenant_capabilities.{json,md}. Read-only; tenant state unchanged.scripts/purple_ai.py: Purple AI natural-language wrapper overPOST /web/api/v2.1/graphql. Non-functional for API tokens,purpleLaunchQuery NATURAL_LANGUAGErequires a browser-session teamToken that service accounts never obtain (confirmed SERVICE_ERROR 2026-05-03). Use the Purple MCP (mcp__purple-mcp__purple_ai) for NL queries. Kept for reference only.scripts/call_purple.py: CLI wrapper forpurple_query(). Non-functional for API tokens (same teamToken constraint). Use the Purple MCP instead.scripts/unified_alerts.py: Unified Alert Management (UAM) GraphQL wrapper overPOST /web/api/v2.1/unifiedalerts/graphql. Covers the full query + mutation surface (list/filter/group/notes/history/trigger-actions). Seereferences/UNIFIED_ALERTS.md.scripts/call_unified_alerts.py: CLI for UAM:python scripts/call_unified_alerts.py list --filter detectionProduct=EDR --first 10,... add-note <id> "…",... set-status --scope <acct> --alert-id <id> RESOLVED.references/UNIFIED_ALERTS.md: UAM reference: operation catalogue, schema quirks, filter patterns, action catalogue, worked recipes.references/TAG_INDEX.md: table of all 111 tags with file pointers and op counts. Start here when you don't know which tag owns an endpoint.references/CAPABILITY_MAP.md: per-tag verb-and-resource summary (L=list, G=get-one, C=count, E=export, A=action, F=filter, S=search, X=mutate) plus an "I want to…" quick lookup. Your fastest orientation when you know the verb but not the path.references/WORKFLOWS.md: ready-to-adapt multi-step recipes: threat triage, endpoint isolation, DV / PowerQuery hunt, RemoteOps, audit trail, tenant capability snapshot, etc. Each lists the endpoints you actually need and the params that matter.references/tenant_capabilities.{json,md}: auto-generated bysmoke_test_queries.py: per-endpoint status (200/403/404/etc.) for the configured tenant. Regenerate whenever the token or tenant changes. The committed copy is a worked example from the Purple demo tenant.references/endpoint_index.json: compact machine-readable index (one entry per op). Used bysearch_endpoints.pybut can be read directly if you need to filter programmatically.references/tags/<Tag>.md: per-tag reference with parameters, descriptions, and required permissions. Load only the files you need.references/common_params.md: shared query params (skip,limit,cursor,sortBy, etc.) and the pagination pattern.references/POWERQUERY_RECIPES.md: PowerQuery / SDL query recipes tested on-tenant: indicator prevalence, PowerShell outbound to public IPs, failed-login triage, storyline activity summary, UAM-indicator SDL crosscheck, endpoint heartbeat. For full PQ language reference use the dedicatedpowerqueryskill.references/detection-rules.md: request-body schema gotchas for Custom Detection / STAR rules, Saved Filters, and PowerQuery Scheduled Detections (queryType / queryLang matrix,isLegacy=falselisting trap, confirmed CREATE / PUT / DELETE bodies, enable / disable endpoints).references/licences.md: setting account and site SKUs, add-ons and licence settings (account first, then site; sitePUTrejectssettings; console label to module name; audit against a golden account and stop on any gap).references/purple-ai.md: Purple AI GraphQL reference (hosts, endpoints, request shapes, operation flows, Python CLI notes, domain boundary, and the confirmed API-token limitations table).references/querying-logs.md: the foolproof PowerQuery / LRQ procedure viascripts/pq.py, plus the UAM, UAM Alert Interface, data-source and schema discovery, baseline anomaly, CTO reporting, common-workflow,s1-secops-mcp, and Hyperautomation operational playbooks.references/python-client.md: fullS1ClientPython usage examples (single page,iter_items, parallelget_manyfan-out, action POSTs).spec/swagger_2_1.json, the original full Swagger spec (14 MB). Use only when the per-tag reference is insufficient, e.g. to resolve a deeply nested request-body schema by$ref. Never read this whole file into context.tests/test_ioc_lifecycle.py: reversible CREATE → LIST → DELETE → VERIFY round-trip for Threat Intelligence IOCs. Uses a unique run-tag per invocation, scopes to a single account, and cleans up before exit. Covers the one "create content" path against the S1 detection surface.tests/test_alerts_dual_api.py: dual-API round-trip for alerts: GraphQL list/detail/addNote/notes/deleteNote plus a parallel REST/cloud-detection/alertsread. Demonstrates that UAM GraphQL is the PRIMARY alert surface and REST is SECONDARY, with the note mutation cleaned up before exit (handles themgmt_note_idpropagation delay).scripts/pq.py: host-only PowerQuery runner over the LRQ API, the Python equivalent of thepowerquery_runMCP tool. Wraps launch/poll/cancel, auth flip toBearer,X-Dataset-Query-Forward-Tagcapture, exponential backoff on 5xx/429/connection errors, and a best-effort cancel. One call:run_pq(client, "<query>", hours=24)returns{row_count, columns, rows, matchCount, ...}. Also exposeslist_data_sources(client, hours=24)(MCP equivalent:powerquery_enumerate_sources). In Cowork, usepowerquery_runinstead.scripts/inspect_source.py: host-only, source-agnostic schema discovery (MCP equivalent:powerquery_schema_discover, orpowerquery_runwithqueryType: "LOG"for a larger sample). For anydataSource.name, samples raw events via the LRQLOGqueryType (or sync/sdl/api/querywhen available) and classifies every attribute the parser emits intoprincipal_user/principal_host/principal_ip/action/temporal/network/file/process/grouping_candidate/other. Picksprim_key+action_keyfrom whatever the source actually carries, so downstream code never hardcodes field names. Exportsdiscover_schema(client, source, hours, sample, extra_filter, backend, escalate)andpick_keys(schema); CLI:python scripts/inspect_source.py --source "<name>" --window 24h. See "Data source + schema discovery" in references/querying-logs.md.scripts/uam_alert_interface.py: host-only UAM (Unified Alert Management) Alert Interface client (MCP equivalent:uam_post_alert) for pushing OCSF alerts INTO UAM viaPOST /v1/alertson the SentinelOne ingest host (e.g.ingest.us1.sentinelone.net, the same host used for log ingest). Handles the gzip-compressed concatenated-JSON body,Bearerauth (the endpoint rejectsApiToken), and theS1-Scopeheader. ExposesUAMAlertInterfaceClient, plusbuild_file_indicator(),build_process_indicator(),build_network_indicator(), andbuild_alert_referencing()payload helpers. URL defaults tohttps://ingest.us1.sentinelone.net; override viaS1_HEC_INGEST_URLfrom the environment or the keychain. Indicators are carried inline in the alert'sfinding_info.related_events[]and sent withpost_alerts([alert], scope=...); the module'spost_indicators()andpost_alert_with_indicators()were removed, because both drove the unreachable/v1/indicatorsendpoint (tombstone comments in the module record why).tests/test_uam_alert_interface_single.pyandtests/test_uam_alert_interface_batch.py: reversible write-side round-trips into UAM. Each makes onepost_alerts([alert], scope=...)call with the indicators carried inline infinding_info.related_events[](one indicator in the single test, three across OCSF 1001/1007/4001 in the batch test), asserts that exactly one request went out, polls UAM GraphQL until the alert surfaces, verifies the indicators and their observables, then closes via bulk-ops with status=RESOLVED and analystVerdict=TRUE_POSITIVE_BENIGN. The worked example in references/querying-logs.md is the same shape.tests/test_ingestion_gateway_alert_with_indicator.py: rename tombstone. Prints a pointer totest_uam_alert_interface_single.pyand exits 2 when invoked directly, guarded sounittest discovercan still import it. There is noscripts/ingestion_gateway.py.scripts/build_source_report.py: collector for the CTO report pipeline. Runs dimension probes + per-principal mix + timeline for a named data source viascripts/pq.pyand writesreports/<slug>_<window>/data.json. Outputs to a per-source subfolder so multiple sources and windows coexist cleanly.scripts/render_charts.py: pure-function renderer.data.jsonin, PNG charts out underreports/<slug>_<window>/charts/. No tenant calls.scripts/build_docx.py/scripts/build_pptx.py: source-agnostic renderers that readdata.jsonand emit<Slug>_CTO_Report_<window>.docxand<Slug>_CTO_Deck_<window>.pptx. Every section is gated ondimsso dimension-sparse sources render cleanly. See "CTO report generation pipeline" in references/querying-logs.md for the full contract and renderer gotchas.reports/<slug>_<window>/: per-run artefact directory. Holdsdata.json,charts/, and the rendered.docx/.pptx. Treat this as the portable unit: move or archive the whole folder.
Using the client in Python (host only)
On the user's host (Claude Code or a terminal), import S1Client from scripts/s1_client.py for anything needing loops, joins, or pagination; use c.get_many([...]) for parallel independent GETs over pooled connections. Credentials come from environment variables or the OS keychain (Python keyring package for Windows Credential Manager). Full worked examples (single page, iter_items, parallel fan-out, and an action POST) are in references/python-client.md. In Cowork, use the s1_api_* MCP tools instead.
Authentication
The API uses header auth: Authorization: ApiToken <token>. The MCP tools and the Python client inject this automatically; do not hand-roll headers.
Token scopes are enforced server-side. Each endpoint in the per-tag references lists Required permissions, if a 403 comes back, the token lacks one of those scopes, and the fix is a new token (not a code change). Surface this clearly to the user.
Rate limits and retries
The MCP server and the Python client retry automatically on 429 and 5xx with exponential backoff (max 30s), honoring Retry-After when present. For bulk operations across thousands of entities, prefer a single filtered action endpoint (/agents/actions/...) over a loop of per-ID calls, the API is designed around filter-based bulk ops.
Destructive actions: confirm first
Many endpoints are destructive or operationally sensitive: disconnect/reconnect agent, uninstall, isolate, shutdown, decommission, script execution via RemoteOps, policy changes, user mutations, account/site deletion. Before firing any POST/PUT/DELETE that affects agents, policies, or tenant config, summarize exactly what will happen (endpoint, filter, estimated scope) and get explicit user confirmation. A 200 response on a wrong filter can isolate thousands of endpoints; there is no undo on many of these.
The safe pattern: run the matching GET with countOnly=true first to show the blast radius, then the mutating call.
Read-only mode. From 1.5.5, s1-secops-mcp is read-only by default (S1_READ_ONLY unset or true). s1_api_put, s1_api_patch, s1_api_delete, uam_add_note, uam_set_status, uam_set_verdict, uam_assign_alert, uam_post_alert and uam_ingest_alert are refused in that mode, and s1_api_post runs only an allowlist of query POSTs and GraphQL query documents. s1_api_get, uam_list_alerts, uam_get_alert and uam_available_actions still run. Check the mode before a multi-step change: read mode.readOnly from the sentinelone://credentials-status resource, or the server's initialize instructions.
- If a write returns "s1-secops-mcp is in read-only mode (S1_READ_ONLY).", stop. Do not retry, and do not route around it with another tool (
s1_api_postto a different endpoint, a hand-built GraphQL mutation, the host Python client). - Tell the user which action was refused and how to enable write: Docker launcher
s1-secops-mcp-launch.sh install --read-write(Windows-ReadWrite), then restart Claude Desktop;.mcpbextension: turn off "Read-only mode (recommended)"; Node install:"S1_READ_ONLY": "false"in the server'senvblock. Detail:docs/read-only.mdin the source repo (not shipped). - Offer what you prepared (request body, rule body, note text, verdict and status) as a local file via
outputFileor the Write tool so nothing is lost. - Never enable write on the user's behalf by editing their MCP client config.
Purple AI: natural-language query, alert summary, and auto-investigation
Purple AI covers natural-language to PowerQuery, per-alert summaries, and auto-investigation across three GraphQL endpoints. For API-token (service-account) workflows, purpleLaunchQuery NATURAL_LANGUAGE and aiInvestigation/run are non-functional because they need a browser-session teamToken; use mcp__purple-mcp__purple_ai instead. purpleAlertSummary (via purple_ai_alert_summary) does work for API tokens. Full endpoint map, GraphQL request shapes, end-to-end operation flows, Python CLI notes, domain boundary, and the API-token limitations table: references/purple-ai.md.
Querying logs via the mgmt console API: the foolproof procedure
Do not hand-roll a requests.post for a PowerQuery. Use the powerquery_run MCP tool, which handles Bearer auth, the /sdl/v2/api/queries path, the mandatory X-Dataset-Query-Forward-Tag, account and site scoping (scope: "<accountId>[:<siteId>]" becomes tenant: false + accountIds plus a site.id term, which is what narrows a global or multi-account token; unscoped runs across every account the token reaches), transient-error retries, and cancel-on-exit. It also takes queryType: "LOG" for raw events with every parsed field (server cap 5000 per query or slice, truncatedByServerCap flags more), slices (2-15) plus merge for long windows, and outputFile for bulk results. powerquery_enumerate_sources lists data sources and powerquery_schema_discover samples a source's fields. On the host, scripts/pq.py (run_pq / list_data_sources) is the Python equivalent. The full playbook (surface-selection table, the 0-rows diagnostic ladder, window-scaling and slicing, LRQ response-shape gotchas, and the pre-run checklist) is in references/querying-logs.md.
UAM, UAM Alert Interface, data-source discovery, reporting, and Hyperautomation
These operational playbooks moved into references/querying-logs.md. Open it when the task is one of:
- Unified Alert Management (UAM), the PRIMARY alert API: the GraphQL alerts inbox via the
uam_*MCP tools (host-only Python:scripts/unified_alerts.py), alert vs threat routing, filter shapes, and blast-radius safety. Deeper schema quirks stay in references/UNIFIED_ALERTS.md. - UAM Alert Interface: pushing OCSF alerts INTO UAM via the ingest host, with indicators carried inline in
finding_info.related_events[], plus the required-field constraints and asset-linkage matrix (references/ASSET_LINKAGE.md). - Data source and schema discovery:
powerquery_enumerate_sourcespluspowerquery_schema_discover(host-only Python:list_data_sources,inspect_source.discover_schema/pick_keys), and the always-exclude-logVolumerule. - Source-agnostic baseline and anomaly detection (
scripts/baseline_anomaly.py). - CTO report generation pipeline (
build_source_report.py,render_charts.py,build_docx.py,build_pptx.py). - Common high-value workflows,
s1-secops-mcpdirect-console usage, and the STAR / Custom Detection rule lifecycle learnings.