Server data from the Official MCP Registry
Syslog receiver and MCP server for homelab log intelligence.
Syslog receiver and MCP server for homelab log intelligence.
Remote endpoints: streamable-http: https://syslog.tootie.tv/mcp
Valid MCP server (1 strong, 1 medium validity signals). No known CVEs in dependencies. Imported from the Official MCP Registry. Trust signals: trusted author (4/4 approved).
Endpoint verified · Requires authentication · 1 issue found
Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.
This plugin requests these system permissions. Most are normal for its category.
Set these up before or after installing:
Environment variable: SYSLOG_HOST
Environment variable: SYSLOG_MCP_TOKEN
Environment variable: SYSLOG_PORT
Remote Plugin
No local installation needed. Your AI client connects to the remote endpoint directly.
Add this to your MCP configuration to connect:
{
"mcpServers": {
"tv-tootie-syslog-mcp": {
"env": {
"SYSLOG_HOST": "your-syslog-host-here",
"SYSLOG_PORT": "your-syslog-port-here",
"SYSLOG_MCP_TOKEN": "your-syslog-mcp-token-here"
},
"url": "https://syslog.tootie.tv/mcp"
}
}
}From the project's GitHub README.
Rust syslog receiver and MCP server for homelab log intelligence. Ingests syslog over UDP and TCP, stores it in SQLite with FTS5 full-text indexing, and exposes action-based log search, inventory, correlation, status, and analysis tools to MCP clients.
┌─────────────────────────────────┐
rsyslog/syslog-ng ─▶ UDP :1514 / TCP :1514 │
network devices ─▶ ┌──────────────────────────┐ │
│ │ parse → batch writer │ │
│ │ SQLite + FTS5 (WAL mode) │ │
│ └──────────────────────────┘ │
Claude / MCP ◀──── ▶ RMCP HTTP :3100/mcp │
local MCP client ◀──▶ syslog mcp query process │
└─────────────────────────────────┘
The daemon listens on a single port for both UDP and TCP syslog (default 1514). All inbound messages are parsed, batched, and written to SQLite with full-text indexing. The MCP HTTP server runs on a separate port (default 3100) and uses RMCP Streamable HTTP in stateless JSON-response mode. Local stdio-only MCP clients can launch syslog mcp, a query-only MCP process that reads the same SQLite database without starting syslog listeners or the HTTP server.
One MCP tool, syslog, is exposed. Use the required action argument to run search, tail, errors, hosts, sessions, search_sessions, abuse, ai_correlate, usage_blocks, project_context, list_ai_tools, list_ai_projects, correlate, stats, status, apps, source_ips, timeline, patterns, context, get, ingest_rate, silent_hosts, clock_skew, anomalies, compare, compose_status, compose_doctor, unaddressed_errors, ack_error, unack_error, notifications_recent, notifications_test, or help.
For the complete action-specific parameter reference, see docs/mcp/SCHEMA.md.
| Action | Purpose |
|---|---|
search | Full-text search with filters |
tail | Recent log entries |
errors | Error/warning summary by host and severity |
hosts | Host registry with first/last seen |
sessions | AI transcript sessions by project |
search_sessions | Ranked grouped session search |
abuse | Abuse hits in AI transcripts with same-session context |
ai_correlate | AI transcript anchors cross-referenced against non-AI logs |
usage_blocks | AI activity in 5-hour UTC windows |
project_context | Summary for one AI project path |
list_ai_tools | Distinct AI tools with counts |
list_ai_projects | Distinct AI projects with counts |
correlate | Cross-host event correlation in a time window |
stats | Database statistics and storage health |
status | Lightweight runtime and DB health |
apps | Distinct application names with log and host counts |
source_ips | Distinct source identifiers with hostname breakdown |
timeline | Bucketed counts over time |
patterns | Near-duplicate message template clusters |
context | Surrounding logs around a log id or timestamp |
get | One log entry by id, including raw frame |
ingest_rate | Recent ingest throughput and write-block state |
silent_hosts | Hosts whose last_seen is older than a threshold |
clock_skew | Per-host received_at minus timestamp distribution |
anomalies | Recent vs baseline volume/error comparison |
compare | Side-by-side comparison of two time ranges |
compose_status | Redacted read-only Compose deployment diagnostics |
compose_doctor | Alias for Compose deployment health diagnostics |
unaddressed_errors | Repeating unacknowledged error signatures |
ack_error | Acknowledge an error signature |
unack_error | Revoke an error acknowledgement |
notifications_recent | Recent notification firings |
notifications_test | Send a test notification via Apprise |
help | Markdown reference for all actions |
syslog searchFull-text search across all syslog messages with optional filters. Uses SQLite FTS5 with porter stemming.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
query | string | no | — | FTS5 search query (see FTS5 query syntax) |
hostname | string | no | — | Exact hostname match. Use syslog with action: "hosts" to enumerate. |
source_ip | string | no | — | Exact source identifier. Syslog entries use the verified network sender address (IP:port); OTLP rows use the verified peer IP; Docker ingest stream rows use docker://host/container/stream; Docker lifecycle event rows use docker-event://host/container/action. |
severity | string | no | — | One of: emerg alert crit err warning notice info debug |
app_name | string | no | — | Application name, e.g. sshd, dockerd, kernel |
from | string | no | — | Start of time range (ISO 8601 / RFC 3339, e.g. 2025-01-15T00:00:00Z) |
to | string | no | — | End of time range (ISO 8601) |
limit | integer | no | 100 | Max results (hard cap: 1000) |
Response
{
"count": 3,
"logs": [
{
"id": 12345,
"timestamp": "2025-01-15T14:30:00Z",
"hostname": "router",
"facility": "kern",
"severity": "err",
"app_name": "kernel",
"process_id": null,
"message": "kernel panic: unable to mount root",
"received_at": "2025-01-15T14:30:01.123Z",
"source_ip": "10.0.0.1:51234"
}
]
}
Examples
query: "kernel panic" # implicit AND: both terms must appear
query: "OOM AND killer" # explicit AND
query: "sshd OR pam" # boolean OR
query: "failed NOT sudo" # boolean NOT
query: '"connection refused"' # exact phrase (bypasses stemming)
query: "error*" # prefix wildcard
query: "restart*" # matches restart, restarted, restarting
syslog tailReturn the N most recent log entries. Equivalent to tail -f across all hosts.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
hostname | string | no | — | Filter to a specific host |
source_ip | string | no | — | Filter to an exact source identifier. Syslog entries use the verified network sender address (IP:port); OTLP rows use the verified peer IP; Docker ingest stream rows use docker://host/container/stream; Docker lifecycle event rows use docker-event://host/container/action. |
app_name | string | no | — | Filter to a specific application |
n | integer | no | 50 | Number of recent entries (hard cap: 500) |
Response
Same structure as syslog search: { "count": N, "logs": [...] }.
syslog errorsSummarize warnings and errors across all hosts in a time window. Groups by hostname and severity, showing counts. Use this for quick health assessments.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
from | string | no | all time | Start of time range (ISO 8601) |
to | string | no | now | End of time range (ISO 8601) |
Severities included: emerg, alert, crit, err, warning.
Response
{
"summary": [
{ "hostname": "router", "severity": "err", "count": 42 },
{ "hostname": "router", "severity": "warning", "count": 17 },
{ "hostname": "storage", "severity": "crit", "count": 3 }
]
}
syslog hostsList all hosts that have sent syslog messages, with first/last seen timestamps and total log counts.
Parameters: none
Response
{
"hosts": [
{
"hostname": "router",
"first_seen": "2025-01-01T00:00:00.000Z",
"last_seen": "2025-01-15T14:30:00.000Z",
"log_count": 18432
}
]
}
syslog sessionsList AI transcript sessions grouped by project, tool, session, and host.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
project | string | no | — | Exact project path, e.g. /home/jmagar/workspace/syslog-mcp |
tool | string | no | — | AI tool filter: claude, codex, or gemini |
hostname | string | no | — | Restrict to one host |
from | string | no | — | Start of time range (ISO 8601) |
to | string | no | — | End of time range (ISO 8601) |
limit | integer | no | 100 | Max sessions (hard cap: 1000) |
Response
{
"count": 1,
"sessions": [
{
"project": "/home/jmagar/workspace/syslog-mcp",
"tool": "codex",
"session_id": "019e1506-dc81-7881-9926-4d6d4efda1ac",
"hostname": "dookie",
"first_seen": "2026-05-11T03:13:51.745Z",
"last_seen": "2026-05-11T04:10:00.000Z",
"event_count": 42
}
]
}
syslog correlateSearch for related events across multiple hosts within a ±N minute window around a reference timestamp. Useful for debugging cascading failures. Results are grouped by host and ordered by time.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
reference_time | string | yes | — | Center timestamp (ISO 8601, e.g. 2025-01-15T14:30:00Z) |
window_minutes | integer | no | 5 | Minutes before and after reference_time (max 60) |
severity_min | string | no | warning | Minimum severity to include. warning returns warning/err/crit/alert/emerg. debug returns everything. |
hostname | string | no | — | Limit correlation to one host |
source_ip | string | no | — | Limit correlation to an exact source identifier. Syslog entries use the verified network sender address (IP:port); OTLP rows use the verified peer IP; Docker ingest stream rows use docker://host/container/stream; Docker lifecycle event rows use docker-event://host/container/action. |
query | string | no | — | FTS5 query to narrow results |
limit | integer | no | 500 | Max total events (hard cap: 999) |
Response
{
"reference_time": "2025-01-15T14:30:00Z",
"window_minutes": 5,
"window_from": "2025-01-15T14:25:00+00:00",
"window_to": "2025-01-15T14:35:00+00:00",
"severity_min": "warning",
"total_events": 12,
"truncated": false,
"hosts_count": 3,
"hosts": [
{
"hostname": "router",
"event_count": 7,
"events": [...]
}
]
}
Note on clock skew: syslog correlate uses the timestamp field from the syslog message, which reflects the sending device's clock. If a device clock is skewed, events may fall outside the correlation window. See Time synchronization.
syslog statsReturn database statistics including total logs, total hosts, time range covered, logical and physical DB size, free disk, configured thresholds, current write-block status, and runtime ingest observability.
Parameters: none
Response
{
"total_logs": 284917,
"total_hosts": 12,
"oldest_log": "2024-10-15T00:00:01Z",
"newest_log": "2025-01-15T14:30:00Z",
"logical_db_size_mb": "312.45",
"physical_db_size_mb": "328.00",
"free_disk_mb": "14200.00",
"max_db_size_mb": 1024,
"min_free_disk_mb": 512,
"write_blocked": false,
"runtime_observability": {
"syslog_udp_packets_received": 280000,
"syslog_tcp_connections_active": 3,
"ingest_entries_enqueued": 284917,
"ingest_queue_depth": 0,
"ingest_queue_capacity": 10000,
"ingest_queue_utilization_pct": "0.00",
"writer_batches_flushed": 2850,
"writer_logs_written": 284917,
"writer_flush_failures": 0,
"writer_logs_retained": 0,
"writer_logs_discarded": 0,
"writer_storage_blocked": false,
"last_ingest_at": "2025-01-15T14:30:05.123Z",
"last_write_at": "2025-01-15T14:30:05.400Z",
"last_error_at": null
},
"otlp": {
"logs_received": 42,
"decode_errors": 0
}
}
write_blocked: true means the storage budget is exceeded and new log ingestion is paused. See Storage budget enforcement.
syslog statusReturn lightweight runtime status without the heavier DB statistics query. Use this for dashboards and doctor checks that need current queue depth, backpressure, writer failure/drop state, listener counters, and last activity timestamps.
Parameters: none
syslog helpReturn markdown documentation for all tools in this toolset.
Parameters: none
The syslog search and syslog correlate actions use SQLite FTS5 with porter stemming (tokenize='porter unicode61'). Valid query forms:
| Syntax | Example | Matches |
|---|---|---|
| Single term | panic | Any message containing "panic" or stemmed variants |
| Porter stemming | restart | restart, restarted, restarting, restarts |
| AND (default) | disk error or disk AND error | Both terms present |
| OR | sshd OR pam | Either term present |
| NOT | failed NOT sudo | "failed" present, "sudo" absent |
| Phrase | "connection refused" | Exact phrase in that order |
| Prefix wildcard | error* | Any word starting with "error" |
| Grouped | (kernel OR oom) AND panic | Grouped boolean logic |
Limits: max 512 characters, max 16 whitespace-separated terms.
Porter stemming means connect, connected, connecting, and connection all match the query connect. Phrase queries ("...") bypass stemming and require exact token order.
Each stored log entry has these fields:
| Field | Type | Description |
|---|---|---|
id | integer | Auto-increment primary key |
timestamp | text | Message timestamp (RFC 3339, UTC). From the syslog message header. |
hostname | text | Hostname from the syslog message (user-controlled, not verified) |
facility | text|null | Syslog facility name (see facilities below) |
severity | text | Syslog severity level name |
app_name | text|null | Application/process name from the syslog message |
process_id | text|null | PID from the syslog message |
message | text | Log message body (FTS5-indexed) |
received_at | text | Server-side receipt timestamp (RFC 3339, UTC). Used for retention. |
source_ip | text | Source identifier. Syslog entries use the exact network sender address (IP:port) captured from the packet/connection peer. OTLP rows use the peer IP without the ephemeral source port. Docker ingest stream rows use docker://host/container/stream; Docker lifecycle event rows use docker-event://host/container/action. |
ai_tool | text|null | AI tool name (e.g. claude, codex) |
ai_project | text|null | AI project path |
ai_session_id | text|null | AI session unique identifier |
ai_transcript_path | text|null | Full path to the source transcript file |
metadata_json | text|null | Source-specific JSON metadata. Syslog rows include parser/source provenance; OTLP rows include resource/log attributes plus trace/span ids; Docker rows include host/container/image/compose/action details; transcript rows include source kind, file path, line number, record key, and scrub status. |
syslog ai index scans the default local transcript roots
~/.claude/projects and ~/.codex/sessions; syslog ai index --path PATH
can scan a known transcript directory or one explicit .jsonl file, and
syslog ai add --file FILE imports one file. Recursive scans are limited to
~/.claude/projects, ~/.codex/sessions, or their children; broad roots such
as /, $HOME, and the current repo root are rejected before walking. The
scanner skips symlinks, counts unsupported non-.jsonl files without parsing
them, and streams transcript files line-by-line in bounded SQLite chunks. Use
--force to reimport a transcript path from scratch after parser changes,
--since RFC3339 to scan only recently modified files, and
syslog ai checkpoints --errors plus syslog ai errors to inspect structured
scanner failures.
For real-time local Claude/Codex transcript ingestion, install the host-local watch service:
syslog setup ai-watch-service install
syslog setup ai-watch-service check
syslog setup ai-watch-service remove
The watcher runs outside Docker because it needs host access to
~/.claude/projects and ~/.codex/sessions. It writes to the configured live
SQLite DB and delegates every stable changed .jsonl file to the same scanner
path used by syslog ai add --file FILE. Installing the watcher disables the
older polling timer so both helpers do not scan the same files.
The optional polling fallback is still available:
syslog setup ai-index-timer install
syslog setup ai-index-timer check
syslog setup ai-index-timer remove
Both helpers are deliberately not inside the Docker container. Docker Compose owns only the server/query runtime.
Imported AI transcript messages are scrubbed for known credential/token patterns
before storage and FTS indexing. The rows still live in the main logs table,
so raw actions such as search, tail, context, and get can return
scrubbed transcript text and local ai_transcript_path values within seconds of
the transcript write. Scrubbing is best-effort, not a compliance boundary. If
storage guardrails cannot recover enough space, indexing fails before committing
additional chunks.
Important: hostname is taken from the syslog message body, which any LAN device can set to an arbitrary value over UDP. For syslog entries, source_ip is the only trustworthy network identifier. For Docker ingest entries, source_ip identifies the configured Docker ingest host/container/stream and should be trusted only as far as the configured docker-socket-proxy endpoint and network path are trusted. metadata_json preserves source-specific context for debugging and correlation, but it is not an authorization boundary. Retention cutoffs use received_at (server clock) so that devices with misconfigured clocks cannot cause premature or indefinite log retention.
Ordered from most to least severe:
| Level | Numeric | Meaning |
|---|---|---|
emerg | 0 | System is unusable |
alert | 1 | Action must be taken immediately |
crit | 2 | Critical conditions |
err | 3 | Error conditions |
warning | 4 | Warning conditions |
notice | 5 | Normal but significant condition |
info | 6 | Informational messages |
debug | 7 | Debug-level messages |
kern, user, mail, daemon, auth, syslog, lpr, news, uucp, cron, authpriv, ftp, ntp, audit, alert, clock, local0–local7.
curl -fsSL https://raw.githubusercontent.com/jmagar/syslog-mcp/main/install.sh | sh
The installer puts the host syslog binary in ~/.local/bin and then runs
syslog setup. Setup is idempotent and owns the shared host layout:
~/.syslog-mcp/.env — secrets, ports, Compose interpolation, runtime values~/.syslog-mcp/compose/docker-compose.yml — Docker Compose deployment assets~/.syslog-mcp/data/syslog.db — SQLite database and WAL/SHM sidecarsSetup writes COMPOSE_PROJECT_NAME=syslog-jmagar-lab so direct
docker compose commands in ~/.syslog-mcp/compose target the same canonical
container as syslog compose.
Useful installer controls:
SYSLOG_INSTALL_DRY_RUN=1 ./install.sh
SYSLOG_INSTALL_PREFIX=/opt/syslog-mcp ./install.sh
SYSLOG_VERSION=0.25.4 ./install.sh
SYSLOG_INSTALL_SKIP_SETUP=1 ./install.sh
Useful setup commands:
syslog setup # first-run or normal repair
syslog setup check # inspect only; does not mutate files or start services
syslog setup repair # repair env/assets and restart the Docker stack
syslog setup ai-watch-service install # host-local real-time transcript watcher
syslog doctor binary # check host/container binary freshness
Install as a Claude Code plugin. The plugin handles deployment automatically — you choose between server mode (this machine hosts the syslog receiver + MCP server) and client mode (connect to a remote server).
Prompted at install time (via userConfig):
| Field | Required | Default | Notes |
|---|---|---|---|
is_server | yes | true | Server mode hosts the receiver; client mode connects to a remote server |
server_url | no | http://localhost:3100 | Server mode: leave default. Client mode: remote host URL (e.g. http://shart:3100) |
api_token | yes | — | Bearer token used by the plugin MCP client. Server mode: this becomes the token the server enforces unless no_auth=true. Client mode: token from the server admin. Stored in the system keychain. |
syslog_host / syslog_port | no | 0.0.0.0 / 1514 | Syslog listener bind (server mode) |
mcp_host / mcp_port | no | 0.0.0.0 / 3100 | MCP HTTP server bind (server mode) |
data_dir | no | ~/.syslog-mcp/data | Optional SQLite directory override; default shared setup data persists outside plugin cache |
max_db_size_mb | no | 8192 | DB size cap; oldest logs deleted when exceeded |
retention_days | no | 90 | 0 = keep forever |
batch_size | no | 100 | Number of parsed messages per SQLite batch |
write_channel_capacity | no | 10000 | Internal parsed-message queue capacity before listener backpressure |
docker_ingest_enabled | no | false | Pull container logs from remote docker-socket-proxy endpoints |
fleet_hosts | no | — | SSH aliases of fleet hosts. Used for Docker ingest (when enabled, each becomes http://<alias>:2375) and the syslog-deploy-dropins skill |
SessionStart hook automation (in server mode):
syslog binary is on PATH; the installer defaults to ~/.local/binSYSLOG_* / SYSLOG_MCP_* environment valuessyslog setup repair, the same setup path used by the one-line installer~/.syslog-mcp and removes stale user-level syslog-mcp.service units/drop-ins left by older plugin versionsBundled skills:
syslog-dr — health check covering MCP, service status, syslog port, fleet drop-ins, and live log flow; tails service logs on failuresyslog-deploy-dropins — SSH-based one-shot rsyslog drop-in deployment to every host in fleet_hostssyslog-redeploy — re-run plugin setup after config or plugin changessyslog-logs — Docker Compose service log tailingsyslog-version-check — check whether the running Docker container matches the local Compose image; add --pull to pull first, otherwise checks only the local image cacheThe plugin deploys the server with Docker Compose through the same syslog setup
path as the one-line installer. You can still build and run the binary locally
for development, but automated deployment is Compose-only.
git clone https://github.com/jmagar/syslog-mcp
cd syslog-mcp
cp .env.example .env
# Edit .env — set SYSLOG_MCP_TOKEN at minimum
docker compose up -d
The container binds:
UDP :1514 and TCP :1514 for syslog ingestionTCP :3100 for the MCP HTTP APIRequires Rust 1.86+.
cargo build --release
./target/release/syslog serve mcp
syslog-mcp supports two auth modes, selectable via SYSLOG_MCP_AUTH_MODE.
Bearer-only (default) — set SYSLOG_MCP_TOKEN and all /mcp requests must present that token as Authorization: Bearer <token>. No OAuth routes are mounted.
Gateway-protected no-auth — set NO_AUTH=true only when an upstream gateway or reverse proxy enforces auth before traffic reaches syslog-mcp. This intentionally disables service-local MCP auth even on non-loopback binds.
OAuth (Google) — set SYSLOG_MCP_AUTH_MODE=oauth, the OAuth provider env vars, and an allowlisted admin email. The server issues RS256 JWTs after users authenticate via Google. Bearer tokens and OAuth JWTs can coexist (OAuth mode disables the static token by default; set SYSLOG_MCP_AUTH_DISABLE_STATIC_TOKEN_WITH_OAUTH=false or disable_static_token_with_oauth = false in config.toml for break-glass access).
Both modes leave /health unauthenticated so health probes always work.
See docs/OAUTH.md for full setup instructions, architecture diagram, and operator FAQ.
Configuration is loaded from three sources in priority order (highest wins):
config.toml (if present)| Variable | Required | Default | Description |
|---|---|---|---|
SYSLOG_MCP_TOKEN | no | — | Bearer token for /mcp. Omit to disable auth. |
SYSLOG_MCP_HOST | no | 0.0.0.0 | Bind host for the MCP HTTP server |
SYSLOG_MCP_PORT | no | 3100 | Bind port for the MCP HTTP server |
SYSLOG_MCP_ALLOWED_HOSTS | no | — | Extra comma-separated Host header values accepted by RMCP Host validation |
SYSLOG_MCP_ALLOWED_ORIGINS | no | — | Extra comma-separated browser origins accepted by RMCP Origin validation |
The plain JSON API is disabled by default. When enabled, it is mounted under /api/* on the same HTTP listener and requires a separate bearer token.
| Variable | Required | Default | Description |
|---|---|---|---|
SYSLOG_API_ENABLED | no | false | Enable the non-MCP JSON API |
SYSLOG_API_TOKEN | yes, when enabled | — | Bearer token for /api/* routes |
| Variable | Required | Default | Description |
|---|---|---|---|
SYSLOG_HOST | no | 0.0.0.0 | Bind host for UDP + TCP syslog listeners |
SYSLOG_PORT | no | 1514 | Bind port for UDP + TCP syslog listeners |
SYSLOG_HOST_PORT | no | 1514 | Docker Compose host port published to container port 1514 |
SYSLOG_MAX_MESSAGE_SIZE | no | 8192 | Max bytes per UDP datagram or newline-delimited TCP frame. Oversized newline-delimited TCP frames are dropped and the connection stays open; oversized unterminated frames are dropped and the connection is closed. |
SYSLOG_MAX_TCP_CONNECTIONS | no | 1024 | Maximum simultaneous TCP syslog connections |
SYSLOG_TCP_IDLE_TIMEOUT_SECS | no | 300 | Idle timeout per TCP read before closing inactive connections |
SYSLOG_BATCH_SIZE | no | 100 | Number of messages per batch write |
SYSLOG_FLUSH_INTERVAL | no | 500 | Batch flush interval in milliseconds |
SYSLOG_WRITE_CHANNEL_CAPACITY | no | 10000 | Internal parsed-message queue capacity |
Optional pull-based Docker log ingestion keeps each remote host on its normal Docker logging driver and has syslog-mcp read container stdout/stderr through read-only docker-socket-proxy endpoints. This avoids configuring Docker's daemon-level syslog driver and does not block container startup when syslog-mcp is down.
| Variable | Required | Default | Description |
|---|---|---|---|
SYSLOG_DOCKER_INGEST_ENABLED | no | false | Enable remote Docker log ingestion |
SYSLOG_DOCKER_HOSTS | one of the two | — | Comma-separated hostnames; each becomes http://<name>:2375 with allow_insecure_http = true. Takes priority over SYSLOG_DOCKER_HOSTS_FILE. |
SYSLOG_DOCKER_HOSTS_FILE | one of the two | — | Path to a TOML file with a [[hosts]] array (use when you need per-host base_url or TLS). If the file does not exist, a warning is logged and no hosts are loaded — the container will not crash. Mount the file via SYSLOG_MCP_CONFIG_VOLUME. |
SYSLOG_DOCKER_RECONNECT_INITIAL_MS | no | 1000 | Initial reconnect delay after host stream failure |
SYSLOG_DOCKER_RECONNECT_MAX_MS | no | 30000 | Maximum reconnect delay after repeated failures |
The hosts file uses this shape:
[[hosts]]
name = "edge-host-a"
base_url = "http://edge-host-a:2375"
allow_insecure_http = true
[[hosts]]
name = "app-host-b"
base_url = "http://app-host-b:2375"
allow_insecure_http = true
The docker-socket-proxy side only needs read access to containers, events, ping, and version endpoints: CONTAINERS=1, EVENTS=1, PING=1, VERSION=1, POST=0. CONTAINERS=1 exposes the broader read-only Docker container API to anything that can reach the proxy, so bind it only on a trusted private network, firewall it to syslog-mcp, or put it behind authenticated TLS. Plain http:// endpoints require allow_insecure_http = true in the hosts file so that this trust decision is explicit.
Docker ingest is intentionally not part of the default smoke test because it needs a live docker-socket-proxy-compatible endpoint and container log stream. For integration testing, run syslog-mcp with SYSLOG_DOCKER_INGEST_ENABLED=true against a disposable docker-socket-proxy or mocked Docker HTTP fixture, emit a unique line from a short-lived container, then verify it with syslog search or mcporter call ... action=search. Container stdout/stderr rows use source_ip=docker://<host>/<container>/<stream>. Container lifecycle rows for actions such as create, start, restart, die, stop, destroy, rename, oom, and health_status:* use source_ip=docker-event://<host>/<container>/<sanitized-action>, facility=docker, and preserve the raw Docker event JSON.
| Variable | Required | Default | Description |
|---|---|---|---|
SYSLOG_MCP_DB_PATH | no | /data/syslog.db | SQLite database path |
SYSLOG_MCP_POOL_SIZE | no | 4 | SQLite connection pool size |
SYSLOG_MCP_RETENTION_DAYS | no | 90 | Days to retain logs. 0 = keep forever. |
SYSLOG_MCP_MAX_DB_SIZE_MB | no | 1024 | Logical DB size trigger for write-blocking. 0 = disabled. |
SYSLOG_MCP_RECOVERY_DB_SIZE_MB | no | 900 | Cleanup target after DB size trigger. Must be less than max. |
SYSLOG_MCP_MIN_FREE_DISK_MB | no | 512 | Free disk trigger for write-blocking. 0 = disabled. |
SYSLOG_MCP_RECOVERY_FREE_DISK_MB | no | 768 | Cleanup target after free disk trigger. Must be greater than min. |
SYSLOG_MCP_CLEANUP_INTERVAL_SECS | no | 60 | Storage budget enforcement interval. Minimum 5. |
SYSLOG_MCP_CLEANUP_CHUNK_SIZE | no | 2000 | Rows deleted per enforcement chunk |
| Variable | Required | Default | Description |
|---|---|---|---|
SYSLOG_UID | no | 1000 | Container user ID for data volume ownership |
SYSLOG_GID | no | 1000 | Container group ID for data volume ownership |
SYSLOG_MCP_DATA_VOLUME | no | syslog-mcp-data | Docker volume name or bind-mount path |
SYSLOG_MCP_CONFIG_VOLUME | no | ./config | Read-only config mount for optional files such as docker-hosts.toml |
DOCKER_NETWORK | no | syslog-mcp | Docker network name (must exist) |
RUST_LOG | no | info | Log level (trace, debug, info, warn, error) |
TZ | no | UTC | Container timezone |
Place config.toml next to the binary (or in the working directory). Environment variables override values set here.
[syslog]
host = "0.0.0.0"
port = 1514
max_message_size = 8192
max_tcp_connections = 512
tcp_idle_timeout_secs = 300
[storage]
db_path = "data/syslog.db"
pool_size = 4
retention_days = 90 # 0 = keep forever
wal_mode = true
max_db_size_mb = 1024
recovery_db_size_mb = 900
min_free_disk_mb = 512
recovery_free_disk_mb = 768
cleanup_interval_secs = 60
[mcp]
host = "0.0.0.0"
port = 3100
server_name = "syslog-mcp"
# api_token = "your-secret-token"
[docker_ingest]
enabled = false
reconnect_initial_ms = 1000
reconnect_max_ms = 30000
[[docker_ingest.hosts]]
name = "edge-host-a"
base_url = "http://edge-host-a:2375"
allow_insecure_http = true
syslog serve mcp # UDP/TCP syslog ingest plus HTTP MCP on /mcp
syslog mcp # query-only MCP stdio transport
syslog setup # install/repair shared ~/.syslog-mcp Docker Compose setup
syslog stats # query the SQLite DB directly from the CLI
syslog db status # inspect SQLite maintenance state
syslog db backup # create a WAL-safe SQLite backup
syslog compose doctor # diagnose live Compose/listener ownership
syslog compose status --json # inspect canonical syslog-mcp container/project
Both modes use the same config and environment variable loader. syslog mcp is for local child-process MCP clients that can read SYSLOG_MCP_DB_PATH; it does not bind network ports or run retention/storage cleanup jobs.
The direct CLI uses the same shared service layer as the MCP tool, so results and validation match the MCP actions without needing an MCP client:
syslog search 'error AND nginx' --hostname proxy --limit 10
syslog tail -n 20 --app-name kernel
syslog errors --from 2026-01-01T00:00:00Z
syslog hosts
syslog correlate --reference-time 2026-01-01T12:00:00Z --window-minutes 10 --severity-min warning
syslog stats --json
syslog db integrity # run PRAGMA integrity_check
syslog db checkpoint --mode full
syslog db vacuum --pages 1000
syslog compose pull # pull image for resolved Compose project
syslog compose up # run docker compose up -d for resolved service
syslog compose restart # restart resolved service
syslog compose logs --tail 20 # bounded compose logs
syslog compose commands resolve the live Compose owner before mutation. They refuse ambiguous cwd fallback, stale Compose labels, listener conflicts, and destructive down without --yes.
See docs/CLI.md for the full direct CLI reference, including flags, JSON output, and how CLI commands map to MCP actions.
The server listens on port 1514 by default. Configure senders to forward to this port. If a device cannot use a non-privileged port, see Exposing port 514.
Create /etc/rsyslog.d/99-remote.conf on each host:
# TCP (reliable, recommended for persistent connections)
*.* @@SYSLOG_SERVER:1514
# UDP (lower overhead, no delivery guarantee)
# *.* @SYSLOG_SERVER:1514
Restart: sudo systemctl restart rsyslog
For hosts running pure journald without rsyslog, first enable forwarding in /etc/systemd/journald.conf:
[Journal]
ForwardToSyslog=yes
Then install and configure rsyslog as above.
Add to /etc/syslog-ng/conf.d/remote.conf:
destination d_remote_tcp {
network("SYSLOG_SERVER"
port(1514)
transport("tcp")
);
};
destination d_remote_udp {
network("SYSLOG_SERVER"
port(1514)
transport("udp")
);
};
log {
source(s_src);
destination(d_remote_tcp);
};
Restart: sudo systemctl restart syslog-ng
Enable systemd in /etc/wsl.conf:
[boot]
systemd=true
Install rsyslog and use the rsyslog config above. Use the Tailscale IP of the syslog-mcp host — WSL has its own network namespace and cannot reach the Docker host IP directly.
Option A — via SSH:
ssh admin@<gateway-ip>
# Create /etc/rsyslog.d/remote.conf (persists on newer firmware):
echo "*.* @SYSLOG_SERVER:1514" | sudo tee /etc/rsyslog.d/remote.conf
sudo systemctl restart rsyslog
Option B — via UI (survives firmware updates):
Settings → System → Advanced → Remote Syslog Server. Set host and port 1514.
Set the syslog server address to your SYSLOG_SERVER and port to 1514 in the device's syslog settings. Most consumer routers and network appliances expose this under Diagnostics or Logging settings.
Syslog's privileged port 514 requires root or CAP_NET_BIND_SERVICE. The recommended approach is to redirect at the host with iptables:
# Redirect UDP and TCP 514 → 1514 on the host
sudo iptables -t nat -A PREROUTING -p udp --dport 514 -j REDIRECT --to-port 1514
sudo iptables -t nat -A PREROUTING -p tcp --dport 514 -j REDIRECT --to-port 1514
# Persist across reboots (Debian/Ubuntu)
sudo apt install iptables-persistent
sudo netfilter-persistent save
For Docker Compose, set SYSLOG_HOST_PORT=514 to publish host port 514 while the container keeps binding unprivileged port 1514. On Unraid, map host port 514 to container port 1514 for both UDP and TCP in the Docker template (514:1514/udp and 514:1514/tcp).
Open the syslog port on the Docker host firewall:
# ufw
sudo ufw allow 1514/udp
sudo ufw allow 1514/tcp
# firewalld
sudo firewall-cmd --permanent --add-port=1514/udp
sudo firewall-cmd --permanent --add-port=1514/tcp
sudo firewall-cmd --reload
Logs are retained for SYSLOG_MCP_RETENTION_DAYS days (default 90). Set to 0 to keep logs forever.
The retention job runs on SYSLOG_MCP_CLEANUP_INTERVAL_SECS (default 60 seconds). It deletes logs in chunks of 10,000 rows, releasing the write lock between chunks so ingest can proceed. Retention cutoff uses received_at (the server-side ingestion timestamp), not the timestamp in the message. This prevents devices with misconfigured clocks from causing premature or indefinite retention.
After large deletions, an incremental FTS5 merge runs to reclaim index space without long write-lock durations.
Two independent guards protect against disk exhaustion:
DB size guard (SYSLOG_MCP_MAX_DB_SIZE_MB, default 1024 MB)
When the logical SQLite DB size exceeds max_db_size_mb, the oldest logs are deleted in chunks of SYSLOG_MCP_CLEANUP_CHUNK_SIZE rows until the size drops below recovery_db_size_mb.
Free disk guard (SYSLOG_MCP_MIN_FREE_DISK_MB, default 512 MB)
When available disk drops below min_free_disk_mb, the oldest logs are deleted until free disk exceeds recovery_free_disk_mb.
Write-blocking behavior
If enforcement cannot free enough space (e.g. the DB is empty but storage is still over limit), the batch writer enters write-blocked state. New log messages accumulate in an in-memory buffer (SYSLOG_WRITE_CHANNEL_CAPACITY, default 10,000 messages). Writes resume automatically when space recovers. The write_blocked field in syslog stats reflects the current state.
Disable either guard by setting its trigger to 0 (also set the recovery target to 0).
Most schema setup runs automatically during startup. Heavy migrations, such as creating a new index on a populated multi-million-row logs table, can hold SQLite's write lock for several minutes before syslog listeners and /health are available. During that window TCP senders may back up and UDP packets may be dropped by kernel buffers.
Before upgrading a populated database:
scripts/backup.sh or sqlite3 /data/syslog.db ".backup /data/syslog-pre-upgrade.db".Migration N: starting ... and Migration N: ... created./health returns ok and syslog stats reports sane counts.See docs/runbooks/deploy.md for the deploy checklist.
Documentation truncated — see the full README on GitHub.
Be the first to review this server!
by Modelcontextprotocol · Developer Tools
Read, search, and manipulate Git repositories programmatically
by Modelcontextprotocol · Developer Tools
Web content fetching and conversion for efficient LLM usage
by Toleno · Developer Tools
Toleno Network MCP Server — Manage your Toleno mining account with Claude AI using natural language.