MCP server for the Panther SIEM, written in Go and packaged for Docker (stdio + Streamable HTTP)
Overview
A Model Context Protocol server for the Panther SIEM, written in Go and shipped as a ~20 MB Docker image. Give it your Panther URL and an API token, and any MCP client (Claude Desktop, Claude Code, Cursor, …) can triage alerts, hunt in the data lake, manage detections, log sources, users and more — , with raw REST/GraphQL escape hatches for anything else. Unofficial project, not affiliated with or endorsed by Panther Labs. - selected with --transport: stdio (client spawns the container) or http (Streamable HTTP, long-running service). - (PANTHER_READ_ONLY=true) registers only tools that cannot change anything. - : update_* tools fetch the current object and merge your changes, so you never wipe fields by accident. - : read-only SQL only; queries on Panther tables must have a time filter unless you opt out; results are paged and size-capped. - Bearer-token auth for the HTTP transport, non-root distroless image, no CGO.
README
panthermcp
A Model Context Protocol server for the Panther SIEM, written in Go and shipped as a ~20 MB Docker image.
Give it your Panther URL and an API token, and any MCP client (Claude Desktop, Claude Code, Cursor, …) can triage alerts, hunt in the data lake, manage detections, log sources, users and more — 85 tools covering every Panther REST endpoint plus the GraphQL API, with raw REST/GraphQL escape hatches for anything else.
Unofficial project, not affiliated with or endorsed by Panther Labs.
- Two transports selected with
--transport:stdio(client spawns the container) orhttp(Streamable HTTP, long-running service). - Read-only mode (
PANTHER_READ_ONLY=true) registers only tools that cannot change anything. - Safe updates:
update_*tools fetch the current object and merge your changes, so you never wipe fields by accident. - Data lake guard rails: read-only SQL only; queries on Panther tables must have a time filter unless you opt out; results are paged and size-capped.
- Bearer-token auth for the HTTP transport, non-root distroless image, no CGO.
Quick start
1. Create a Panther API token
In Panther go to Settings → API Tokens → Create New Token and grant the permissions you
want the assistant to have (e.g. Read Alerts, Manage Alerts, Query Data Lake, View Rules,
View Log Sources, Read Panther Metrics, Read User Info). The server can only do what the token allows;
the get_permissions tool shows what it has.
2. Build the image
docker build -t panthermcp .
Or pull the prebuilt multi-arch image: ghcr.io/pmind12/panthermcp:latest.
3a. Use it over stdio (Claude Desktop / Claude Code)
The client starts the container itself. Claude Desktop, claude_desktop_config.json:
{
"mcpServers": {
"panther": {
"command": "docker",
"args": ["run", "-i", "--rm",
"-e", "PANTHER_URL", "-e", "PANTHER_API_KEY",
"ghcr.io/pmind12/panthermcp:latest", "--transport", "stdio"],
"env": {
"PANTHER_URL": "https://your-instance.runpanther.net",
"PANTHER_API_KEY": "your-api-token"
}
}
}
}
Claude Code:
claude mcp add panther -e PANTHER_URL=https://your-instance.runpanther.net -e PANTHER_API_KEY=your-api-token -- docker run -i --rm -e PANTHER_URL -e PANTHER_API_KEY ghcr.io/pmind12/panthermcp:latest --transport stdio
3b. Run it as an HTTP service
cp .env.example .env # fill in PANTHER_URL, PANTHER_API_KEY and MCP_AUTH_TOKEN
docker compose up -d
or without compose:
docker run -d -p 8080:8080 -e PANTHER_URL=https://your-instance.runpanther.net -e PANTHER_API_KEY=your-api-token -e MCP_AUTH_TOKEN=a-long-random-string ghcr.io/pmind12/panthermcp:latest --transport http
The MCP endpoint is http://:8080/mcp (health check: /healthz). Clients send
Authorization: Bearer . For example with Claude Code:
claude mcp add --transport http panther http://localhost:8080/mcp --header "Authorization: Bearer a-long-random-string"
Put it behind TLS (a reverse proxy) if it is reachable from other machines.
Configuration
Every flag can also be set by environment variable.
| Flag | Env var | Default | Description |
|---|---|---|---|
--transport |
MCP_TRANSPORT |
stdio |
stdio or http |
--panther-url |
PANTHER_URL |
— | Your Panther URL (see below) |
| — | PANTHER_API_KEY |
— | Panther API token (required) |
--read-only |
PANTHER_READ_ONLY |
false |
Only register non-mutating tools |
--addr |
MCP_ADDR |
:8080 |
HTTP listen address |
--path |
MCP_PATH |
/mcp |
HTTP endpoint path |
| — | MCP_AUTH_TOKEN |
— | Bearer token clients must send to the HTTP transport (required for http) |
--insecure-no-auth |
MCP_INSECURE_NO_AUTH |
false |
Allow http without MCP_AUTH_TOKEN (only behind your own auth proxy) |
| — | PANTHER_REST_URL |
derived | Override the REST API base URL |
| — | PANTHER_GRAPHQL_URL |
derived | Override the GraphQL endpoint |
--log-level |
LOG_LEVEL |
info |
debug, info, warn, error (logs go to stderr) |
Which URL? Any of these work for PANTHER_URL:
- the console URL,
https://acme.runpanther.net— the API endpoint is read from the console’s config, falling back tohttps://api.acme.runpanther.net; - the API URL shown under Settings → API Tokens, e.g.
https://api.acme.runpanther.netor.../public/graphql; - a Cloud Connected / self-hosted API URL ending in
/v1.
If your deployment is unusual, set PANTHER_REST_URL / PANTHER_GRAPHQL_URL explicitly.
The resolved endpoints are logged at startup and returned by the get_panther_info tool.
Tools
| Area | Tools |
|---|---|
| Alerts | list_alerts, get_alert, get_alert_events, update_alert, bulk_update_alerts |
| Alert comments | list_alert_comments, get_alert_comment, add_alert_comment, update_alert_comment |
| Alert context tags | list_alert_context_tags, create_alert_context_tag, rename_alert_context_tag, delete_alert_context_tag |
| Panther AI | start_ai_alert_triage, get_ai_alert_triage_summary |
| Detections (rules, scheduled rules, simple rules, policies, correlation rules) | list_detections, get_detection, create_detection, update_detection, test_detection, set_detection_enabled, delete_detection, bulk_delete_detections |
| Data models | list_data_models, get_data_model, create_data_model, update_data_model, delete_data_model |
| Global helpers | list_global_helpers, get_global_helper, create_global_helper, update_global_helper, delete_global_helper |
| Saved / scheduled queries | list_saved_queries, get_saved_query, create_saved_query, update_saved_query, delete_saved_query |
| Data lake | execute_data_lake_query, get_data_lake_query_results, cancel_data_lake_query, list_data_lake_queries, list_databases, list_tables, get_table_schema, search_indicators |
| Log sources | list_log_sources, get_log_source, get_log_source_config, create_log_source, update_log_source, delete_log_source |
| Source alarms | get_log_source_alarm, set_log_source_alarm, delete_log_source_alarm |
| Log type schemas | list_log_type_schemas, get_log_type_schemas, create_or_update_log_type_schema, set_log_type_schema_archived |
| Cloud accounts | list_cloud_accounts, get_cloud_account, create_cloud_account, update_cloud_account, delete_cloud_account |
| Metrics | get_metrics (alerts per severity/rule, events & bytes processed/queried/ingested, latency, totals) |
| Users & access | list_users, get_user, create_user, update_user, delete_user, list_roles, get_role, create_role, update_role, delete_role |
| API tokens | list_api_tokens, get_api_token, create_api_token, update_api_token, delete_api_token, rotate_api_token, get_permissions |
| Instance | get_panther_info |
| Escape hatches | panther_rest_request, panther_graphql_query |
Every tool carries MCP annotations (readOnlyHint, destructiveHint), so clients can ask
for confirmation before anything that deletes data. In read-only mode the escape hatches
stay available but only allow GET requests and GraphQL queries (no mutations).
Example prompts
- “Show me all open HIGH and CRITICAL alerts from the last 24 hours and summarise them.”
- “Investigate alert
abc123: get its events, then look for other activity from the same IP in CloudTrail over the past week.” - “Which log sources are unhealthy, and when did each last receive data?”
- “Disable the rule
AWS.Console.RootLoginand add a comment to its open alerts explaining why.” - “Search all logs for
198.51.100.7andevil.example.comin the last 30 days.”
Security notes
- The API token is the security boundary: give it the least privilege you need, and prefer
PANTHER_READ_ONLY=truefor exploratory use. - The HTTP transport refuses to start without
MCP_AUTH_TOKEN(unless you explicitly pass--insecure-no-auth); without it anyone who can reach the port could use your Panther token. - The API key is never sent across a redirect to another host, and an API URL discovered from the console page is only trusted if it is HTTPS on the console’s own domain.
execute_data_lake_queryaccepts only a single read statement (SELECT,WITH,SHOW,DESCRIBE,EXPLAIN).- Tool output contains data from your logs and alerts. Treat it as untrusted input to the model (log lines can contain prompt-injection attempts), and keep a human in the loop for destructive actions.
get_permissionsnever returns the token value.
Development
Requires Go 1.25+ (or just Docker).
go test -race ./...
go run ./cmd/panthermcp --transport stdio
Without a local Go toolchain:
docker run --rm -v "$PWD:/src" -w /src golang:1.25 go test ./...
The tests drive the tools through a real MCP client session against a fake Panther API, and the GraphQL documents the tools send have been validated against Panther’s published schema.
Layout:
cmd/panthermcp/ entrypoint, flags, stdio / HTTP transports
internal/panther/ REST + GraphQL client, endpoint discovery
internal/tools/ MCP tool definitions, one file per area
License
Install
docker run -i --rm -e PANTHER_URL -e PANTHER_API_KEY ghcr.io/pmind12/panthermcp:latest --transport stdioConfiguration
{
"mcpServers": {
"panther": {
"command": "docker",
"args": ["run", "-i", "--rm",
"-e", "PANTHER_URL", "-e", "PANTHER_API_KEY",
"ghcr.io/pmind12/panthermcp:latest", "--transport", "stdio"],
"env": {
"PANTHER_URL": "https://your-instance.runpanther.net",
"PANTHER_API_KEY": "your-api-token"
}
}
}
}