100% free and open source · every tool, every host, no licence key · read the licence
Secronyx

Start

Connect an AI client

Attach Claude Code, Claude Desktop and other MCP clients over stdio, reach a remote server over SSH or HTTP, and present each supported credential type.

Secronyx speaks the Model Context Protocol in two ways. Over stdio the client spawns the binary and exchanges newline-delimited JSON-RPC 2.0 on its stdin and stdout; nothing listens on the network and the operating system's process controls are the only access control. Over HTTP the server listens on an address you choose and accepts the same JSON-RPC as POST /, protected by whichever authentication method you configure. This page shows how to attach real clients to each, and what they see once connected. Install the binary first (Installation) and read Getting started if you want to see the protocol exchange by hand.

Local clients over stdio

Claude Code

# Linux and macOS
claude mcp add --transport stdio secronyx -- /usr/local/bin/secronyx

# Windows
claude mcp add --transport stdio secronyx -- C:\path\to\secronyx-windows-amd64.exe

Verify with claude mcp list and, inside a session, /mcp. Then ask in plain language: "What is using the CPU on this machine?", "Show the top processes by memory", "Are any systemd units failed?", "What is listening on the network?". The client chooses tools such as get_cpu_info, get_processes, get_failed_units and get_listening_ports from the tools/list catalogue and calls them with arguments taken from their input schemas.

Claude Desktop and other stdio clients

Any client that can spawn a process and speak MCP on its pipes works the same way. Claude Desktop reads a JSON configuration with an mcpServers map; the entry for Secronyx needs only the binary path:

{
  "mcpServers": {
    "secronyx": {
      "command": "/usr/local/bin/secronyx",
      "args": []
    }
  }
}

Consult your client's own documentation for where that file lives; the shape above is what every stdio client needs, whatever the file is called.

Passing server flags through the client

Anything after the binary path is passed to the server, so you configure it in the client entry. Two flags are worth setting for an interactive client:

  • --audit-output <path>: the default audit file is /var/log/secronyx/audit.jsonl, which an unprivileged desktop user usually cannot create. The server then writes audit events to stderr, which most clients capture into their own logs. Give it a writable path instead.
  • --scopes <list>: limit the catalogue to what this client needs, for example core,logs,hooks,triage. Fewer tools also means a smaller tools/list for the model to read.
claude mcp add --transport stdio secronyx -- /usr/local/bin/secronyx \
  --audit-output "$HOME/.secronyx/audit.jsonl" \
  --scopes core,logs,hooks,triage,software
{
  "mcpServers": {
    "secronyx": {
      "command": "/usr/local/bin/secronyx",
      "args": ["--audit-output", "/Users/you/.secronyx/audit.jsonl", "--enable-sensitive"]
    }
  }
}

--enable-sensitive registers the nine tools that read authentication logs, environment variables, process environments, user accounts, sudo and SSH configuration, certificates, MAC status and macOS privacy permissions. They are off by default for a reason; enable them for a client only when the host and the audit trail justify it. See Scopes and authorization.

Keep stdout clean: the server writes logs and audit fallbacks to stderr only, so a wrapper script that prints to stdout will break the protocol.

A remote host over SSH

SSH already authenticates and encrypts, so the simplest remote setup is to make ssh the client's command and the binary the remote command. The remote side runs in stdio mode and opens no listener:

{
  "mcpServers": {
    "secronyx-web01": {
      "command": "ssh",
      "args": ["ops@web01.example.com", "/usr/local/bin/secronyx"]
    }
  }
}

From a shell the same pattern gives ad-hoc answers without any client:

ssh ops@web01.example.com "secronyx --query get_cpu_info --json"

The remote Unix user's permissions bound what the collectors can read, and the audit log is written on the remote host. Stdio has no authenticated identity, so those audit events carry no identity or client IP. For many hosts, Fleet deployment and Hybrid and SaaS mode scale better than one SSH entry per machine.

Inside Docker

The image's entrypoint is the binary and its default is stdio, so a container can be the client's command. Build it once (docker compose build secronyx-stdio or docker build -t secronyx:latest . from the repository) and then:

{
  "mcpServers": {
    "secronyx-container": {
      "command": "docker",
      "args": ["run", "-i", "--rm",
               "-v", "/proc:/host/proc:ro", "-v", "/sys:/host/sys:ro",
               "secronyx:latest", "--audit-output", "/dev/stderr"]
    }
  }
}

The container sees its own process namespace unless you add --pid=host; the Compose file's privileged profile shows the full set of mounts and options. See Docker and Compose.

Remote clients over HTTP

Start the server with --transport http. The endpoint is /, it accepts POST only (any other method gets 405 Method not allowed), one JSON-RPC request per body, and replies with one JSON object. There is no session header and no server-push stream; a client that opens a GET event stream will get 405 and must use plain POST. Three more paths exist: GET /health returns {"status":"ok"} without authentication, GET /metrics serves Prometheus metrics behind the same credentials as /, and GET /.well-known/oauth-protected-resource serves RFC 9728 metadata built from --server-url, which names the authorization server when OIDC or introspection is configured.

Claude Code over HTTP

claude mcp add --transport http secronyx-web01 https://web01.example.com:8443 \
  --header "Authorization: Bearer $SECRONYX_TOKEN"

Any credential in the table below can be supplied as a --header. For mutual TLS the client must also present a certificate, which is a client-side TLS setting rather than a header.

Credentials the server accepts

Method What the client sends Server flags
Static bearer token Authorization: Bearer <token> (32+ characters) --token or SECRONYX_TOKEN
OIDC JWT Authorization: Bearer <jwt> --oidc-issuer, --oidc-audience
OAuth introspection Authorization: Bearer <opaque token> --auth-server, --client-id, --client-secret
API key X-API-Key: msk_<id>_<secret> or Authorization: ApiKey msk_<id>_<secret> --api-keys-file
SSH signature Authorization: SSH-Sig keyid="...", ts="...", nonce="...", sig="..." --ssh-authorized-keys, --ssh-ca-keys
Mutual TLS a client certificate in the TLS handshake --tls-client-ca, --mtls-identity-file

The server tries API keys, then SSH signatures, then mutual TLS, and only then the bearer-token methods; the first authenticator that recognises a credential decides. A static token grants every scope (*). The other methods carry scopes with the identity: an OIDC or introspected token's scope claim (bare names such as core or namespaced as mcp:tools:core), the scopes recorded with an API key, the scopes="..." option on an authorized_keys line, or the mapping in the mTLS identity file. Details are in Authentication.

Bearer token with curl

export SECRONYX_TOKEN="$(openssl rand -base64 32)"
secronyx --transport http --listen 0.0.0.0:8443 \
  --server-url https://web01.example.com:8443 \
  --tls-cert /etc/mcp/cert.pem --tls-key /etc/mcp/key.pem

curl -s https://web01.example.com:8443/ \
  -H "Authorization: Bearer $SECRONYX_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_processes","arguments":{"limit":5,"sort_by":"memory"}}}'

A rejected credential returns HTTP 401 with a JSON body and a challenge header pointing at the resource metadata:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="secronyx", resource_metadata="https://web01.example.com:8443/.well-known/oauth-protected-resource"
Content-Type: application/json

{"error":"unauthorized","error_description":"invalid token"}

The error_description is the authenticator's reason (missing Authorization header, invalid Authorization header format, invalid token, or the message from the OIDC, introspection, API key, SSH or mTLS check). Every failure is also written to the audit log with the client IP.

API keys

Create a key on the server; the secret is printed once:

sudo secronyx apikey create --file /etc/secronyx/keys.json \
  --name alice --scopes core,logs,triage --expires 90d --cidr 10.0.0.0/8
secronyx --transport http --listen 0.0.0.0:8443 \
  --tls-cert /etc/mcp/cert.pem --tls-key /etc/mcp/key.pem \
  --api-keys-file /etc/secronyx/keys.json
curl -s https://web01.example.com:8443/ \
  -H "X-API-Key: msk_3fa9c1e2_..." \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

apikey list --file <path> shows ids, names, status, creation and expiry times and scopes (never secrets), and apikey revoke --file <path> --id <id> disables one. See Tutorial: per-operator API keys.

SSH-signed requests

Operators who already hold SSH keys can sign each request instead of carrying a token. The ssh-sign subcommand produces the header, or a complete curl command, for a given URL and body:

secronyx ssh-sign --url https://web01.example.com:8443/ \
  --key ~/.ssh/id_ed25519 --body request.json --curl

--agent signs with a key from ssh-agent (--fingerprint selects one), --cert attaches an SSH certificate, --passphrase-env names a variable holding the key passphrase, and --body - reads the body from stdin. The server verifies against --ssh-authorized-keys (an authorized_keys-format file whose lines may carry scopes="core,logs", expires, from or principals; any other option is rejected) or --ssh-ca-keys. The signed string covers the HTTP method, the request path and query, the host, a timestamp, a per-request nonce and the SHA-256 of the body, so a captured header cannot be replayed or re-targeted; a certificate travels in an extra cert="..." field. See Tutorial: SSH key request signing.

Mutual TLS

With --tls-client-ca the listener demands a certificate chaining to that bundle before reading any HTTP request; --mtls-identity-file maps certificates (by SPKI, SPIFFE ID, DNS name, CN or OU) to names and scopes. The client presents its certificate as a TLS setting:

curl -s https://web01.example.com:8443/ \
  --cert alice.crt --key alice.key --cacert internal-ca.pem \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_uptime"}}'

See Tutorial: mutual TLS end to end.

Example clients

The repository's examples/ directory holds a Go and a Python client that wrap tools/list and tools/call over HTTP with an optional bearer token:

go run examples/go/main.go -url https://web01.example.com:8443 -token "$SECRONYX_TOKEN" -list
go run examples/go/main.go -url https://web01.example.com:8443 -token "$SECRONYX_TOKEN" -query get_cpu_info

pip install requests
python examples/python/mcp_client.py --url https://web01.example.com:8443 --token "$SECRONYX_TOKEN" --query get_processes

Both default to http://localhost:8080, which matches an unauthenticated loopback server started with secronyx --transport http.

What a connected client sees

  • tools/list returns every registered tool, filtered on the HTTP transport to the tools the caller's scopes permit. On stdio there is no identity, so the list is the whole registration.
  • tools/call on a tool the identity may not use fails with a JSON-RPC error whose message is Forbidden; on a tool that was never registered (for example a sensitive tool without --enable-sensitive) the message is Tool not found. Both outcomes are audited.
  • Results are JSON documents inside text content. Credentials, tokens and keys that appear in system state are redacted before the result leaves the server unless it was started with --no-redact. See Redaction.
  • Each call is one audit event carrying the tool name, arguments, duration, result and, over HTTP, the authenticated identity and client IP. See Audit logging.

Checking the connection

  • claude mcp list shows the configured servers; /mcp inside Claude Code shows whether the server started and how many tools it exposes.
  • For a stdio client that fails to start, run the exact command from the client configuration in a terminal and send {"jsonrpc":"2.0","id":1,"method":"ping"} on stdin; the server replies {"jsonrpc":"2.0","id":1,"result":{}}. Everything on stderr is the server's own log.
  • For HTTP, curl -s https://host:8443/health proves the listener and TLS; a 401 on / with WWW-Authenticate proves the listener but not the credential; the error_description says why.
  • A server that exits immediately when bound to a non-loopback address is enforcing the exposure rules: it needs authentication and TLS, or --allow-unauthenticated on an isolated network. See Network and path policy and Troubleshooting.

Built 2026-09-19. Source: levantar-ai/secronyx. Found a mistake? Tell us.