npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@journal-labs/bastion

v0.1.1

Published

Outbound bastion for connecting MCP servers and skills to Journal

Readme

Journal Bastion

Connect your data sources to Journal. The bastion runs inside your network and connects outbound to Journal. Credentials stay in your infrastructure, and no inbound ports are required.

How It Works

+-------------------------------------+
|           Your Network              |
|                                     |
|  +--------------+  +-------------+  |
|  | Data sources |<-+   Bastion   |  |
|  +--------------+  +------+------+  |
|                           |         |
+---------------------------+---------+
                            | secure outbound
                     +------v------+
                     |   Journal   |
                     +-------------+

Quick Start

npm

npm install -g @journal-labs/bastion

JOURNAL_BASTION_TOKEN=gw_your_token journal-bastion --config bastion.json

Docker

To validate the Docker image locally, create a .env file next to bastion.json that contains JOURNAL_BASTION_TOKEN=gw_your_token, then run:

docker run --rm \
  -v "$(pwd)/bastion.json:/etc/journal/bastion.json:ro" \
  --env-file .env \
  ghcr.io/endurancelabs/journal-bastion:latest --config /etc/journal/bastion.json

For a long-lived container, mount a persistent config file and env file:

docker run -d --name journal-bastion --restart unless-stopped \
  -v /etc/journal/bastion.json:/etc/journal/bastion.json:ro \
  --env-file /etc/journal/bastion.env \
  ghcr.io/endurancelabs/journal-bastion:latest --config /etc/journal/bastion.json

The image ENTRYPOINT is the bastion binary. Pass bastion flags such as --config, --env-file, and --version after the image name. Provide secrets with --env-file or -e; tokens and integration credentials should not be included in the image. Config hot-reload over a bind mount is reliable on Linux hosts. On Docker Desktop for macOS or Windows, restart the container after editing the config file.

Example config file (bastion.json)

{
  "mcpServers": [
    {
      "id": "postgresql",
      "command": "npx",
      "args": ["-y", "@toolbox-sdk/server", "--prebuilt", "postgres", "--stdio"],
      "name": "PostgreSQL",
      "description": "Query a PostgreSQL database",
      "envVars": {
        "POSTGRES_HOST": "POSTGRES_HOST",
        "POSTGRES_PORT": "POSTGRES_PORT",
        "POSTGRES_DATABASE": "POSTGRES_DATABASE",
        "POSTGRES_USER": "POSTGRES_USER",
        "POSTGRES_PASSWORD": "POSTGRES_PASSWORD"
      }
    },
    {
      "id": "remote-api",
      "transport": "streamable-http",
      "url": "https://mcp.example.com/mcp",
      "name": "Remote API",
      "description": "Remote MCP server",
      "headers": { "Authorization": "REMOTE_MCP_AUTHORIZATION" }
    }
  ],
  "skillsDir": "/opt/journal/skills"
}

MCP server packages in examples are external runtime commands. They are resolved by npx when the bastion starts and are not bundled with, or installed by, journal-bastion.

Set every host environment variable referenced by an envVars key or a headers value before starting the bastion. For the config above, that means POSTGRES_* and REMOTE_MCP_AUTHORIZATION in addition to JOURNAL_BASTION_TOKEN.

Runnable examples (this config plus minimal TS and Python client servers) live in examples/. Database and enterprise integration examples live in examples/integrations/. Add a "$schema" field pointing at spec/bastion-config.schema.json for editor autocomplete and validation:

{
  "$schema": "https://raw.githubusercontent.com/EnduranceLabs/journal-bastion/main/spec/bastion-config.schema.json",
  "mcpServers": []
}

Run journal-bastion --help for the full list of flags and environment variables.

Configuration

Environment variables

| Variable | Required | Default | Description | |----------|----------|---------|-------------| | JOURNAL_BASTION_TOKEN | yes | — | Auth token from Journal (starts with gw_) | | JOURNAL_BASTION_URL | no | wss://bastion.journal.one | Journal endpoint. Set this only to reach a different service; an explicit path is sent as given. | | JOURNAL_BASTION_CONFIG | no | — | Path to config file, or inline JSON (detected by leading {) | | JOURNAL_BASTION_ENV_FILE | no | — | Path to .env file (auto-detects .env in cwd if not set) | | LOG_LEVEL | no | info | Log level: debug, info, warn, error |

Config file

The config file describes what the bastion offers. Point to it with either:

  1. --config /path/to/bastion.json — CLI argument (highest precedence)
  2. JOURNAL_BASTION_CONFIG — env var containing a file path or inline JSON

Both mcpServers and skillsDir are optional. An empty {} is valid; the bastion will connect without exposing tools or skills.

Use --env-file /path/to/.env to load environment variables from a .env file. If neither --env-file nor JOURNAL_BASTION_ENV_FILE is set, the bastion auto-detects a .env file in the current directory. Values from .env are used only when the variable is not already set in the process environment.

Config file schema

| Field | Type | Default | Description | |-------|------|---------|-------------| | mcpServers | array | [] | MCP server definitions (see below) | | skillsDir | string \| null | null | Path to directory containing skill Markdown files |

MCP servers

MCP (Model Context Protocol) is a standard for connecting AI agents to external tools. The bastion connects to MCP servers via three transports, making their tools available to Journal.

Each entry in mcpServers has a transport field that determines the connection type. Configs without a transport field that have a command are treated as stdio for backward compatibility.

Common fields (all transports):

| Field | Required | Description | |-------|----------|-------------| | id | yes | Unique name for this server | | transport | no | "stdio" (default), "sse", or "streamable-http" | | name | no | Display name (defaults to id) | | description | no | Server description |

stdio — local subprocess (default):

| Field | Required | Description | |-------|----------|-------------| | command | yes | Command to run (e.g. npx, python) | | args | no | Command-line arguments | | envVars | no | Map of { hostEnvVar: subprocessEnvVar } resolved before starting the subprocess |

sse — SSE client (legacy remote servers):

| Field | Required | Description | |-------|----------|-------------| | url | yes | SSE endpoint URL | | headers | no | Map of { headerName: hostEnvVar } — values resolved from host environment |

streamable-http — Streamable HTTP client (recommended for remote servers):

| Field | Required | Description | |-------|----------|-------------| | url | yes | HTTP endpoint URL | | headers | no | Map of { headerName: hostEnvVar } — values resolved from host environment |

Skills

Skills are instructions that teach Journal how to perform specific tasks in your environment. Place Markdown files in a directory and set skillsDir in the config file. Each .md file becomes a skill — the filename is used as the skill name.

Protocol

The bastion communicates with Journal over a WebSocket using a simple JSON protocol (version 2). The full specification is in spec/protocol.md; this section covers the key ideas.

Connection flow

The bastion connects outbound to the Journal service — no inbound ports are needed. After authenticating with a token, it sends a version_changed message announcing its current version hashes. The connection is then ready — no registration handshake needed. The service decides when to fetch tools and skills by sending pull requests (get_tools, get_skills).

Change detection

Tools and skills can change while the bastion is running. An MCP server might restart with different tools, or a skill file might be added to disk. The bastion detects these changes automatically and sends a lightweight version_changed message with updated version hashes. The service can then pull the specific data it needs.

The bastion also watches the config file and .env file for changes. When you add, remove, or modify an MCP server in the config file, the bastion automatically starts, stops, or restarts the affected servers — no bastion restart required. Similarly, when an environment variable in the .env file changes, any MCP servers that depend on it are automatically restarted. Note that skillsDir changes are not hot-reloaded and require a bastion restart.

Version hashes (mcpVersion and skillsVersion) are content-based (SHA-256, 16 hex chars). The same content produces the same hash across restarts, so the service can distinguish real catalog changes from bastion restarts.

An MCP server that fails to start, for example because of an invalid command or unreachable URL, is logged and skipped. The bastion still connects and serves healthy servers and skills, so one misconfigured server does not make the bastion unavailable.

What clients should do

Services using the client libraries (TypeScript or Python) receive onBastionConnected after the initial pull completes (integrations are already populated). When the bastion sends version_changed, the client auto-pulls what changed and fires onBastionUpdated.

Services can also explicitly pull at any time using getVersions(), getTools(), or getSkills() on a specific bastion. Both client libraries expose the same optional hooks for observability: getTraceContext / get_trace_context propagates a W3C trace context onto each tool call, and onSocketError / on_socket_error surfaces socket-level and unexpected connection-handler failures (the libraries never write to the console themselves).

Telemetry & Audit

The bastion can emit OpenTelemetry traces and metrics to a customer-controlled OTLP/HTTP endpoint. It also records audit metadata for transparency: tool calls (integration, tool, request id, outcome, duration), outbound messages to Journal (message type and request id), config/env reloads, and MCP process start/stop. No secrets, tool arguments, or payload bodies are recorded.

Enabling telemetry

Telemetry is off unless an OTLP endpoint is provided.

| Variable | Default | Description | |----------|---------|-------------| | OTEL_EXPORTER_OTLP_ENDPOINT | — | OTLP/HTTP endpoint (e.g., https://otel.example.com) to enable traces/metrics | | OTEL_SERVICE_NAME | journal-bastion | Service name reported in telemetry | | TELEMETRY_DISABLED | false | Set to true to force-disable telemetry | | AUDIT_LOG_FILE | — | Path to a local JSONL audit file (metadata only) | | AUDIT_MAX_BYTES | — | Rotate audit file when it exceeds this size (bytes) | | AUDIT_MAX_FILES | — | Number of rotated audit files to keep |

Example:

OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.example.com \
OTEL_SERVICE_NAME=journal-bastion-prod \
AUDIT_LOG_FILE=/var/log/journal-bastion-audit.log \
JOURNAL_BASTION_TOKEN=gw_your_token \
JOURNAL_BASTION_CONFIG=/etc/journal/bastion.json \
journal-bastion --config /etc/journal/bastion.json

License

MIT