MCP Hub
Back to servers

jwt-lab

jwt-lab – A fast, secure, beautiful JWT CLI tool and MCP server for developers & AI agents. Encode, decode, verify, inspect, audit, and generate keys for JSON Web Tokens.

npm416/wk
Updated
Apr 9, 2026

Quick Install

npx -y jwt-lab

     ██╗██╗    ██╗████████╗   ██╗      █████╗ ██████╗ 
     ██║██║    ██║╚══██╔══╝   ██║     ██╔══██╗██╔══██╗
     ██║██║ █╗ ██║   ██║█████╗██║     ███████║██████╔╝
██   ██║██║███╗██║   ██║╚════╝██║     ██╔══██║██╔══██╗
╚█████╔╝╚███╔███╔╝   ██║      ███████╗██║  ██║██████╔╝
 ╚════╝  ╚══╝╚══╝    ╚═╝      ╚══════╝╚═╝  ╚═╝╚═════╝
────────────────────────────────────────────────────────
v0.1.0 · JWT toolkit for developers & AI agents

jwt-lab

The JWT Swiss-Army Knife for Developers & AI Agents

npm version license node TypeScript CI CodeQL tests MCP

Encode · Decode · Verify · Inspect · Explain · Keygen · MCP Server

A fast, secure, beautiful, and AI-agent-ready command-line tool for working with JSON Web Tokens (JWTs), plus a full Model Context Protocol (MCP) HTTP/JSON server.

Installation · Quick Start · Commands · MCP Server · Configuration · API Reference


Why jwt-lab?

Featurejwt-labjwt.ioOther CLIs
🔐 Security linting & audit✅ 6 built-in rules
🤖 AI-native MCP server✅ Full HTTP/JSON API
🗣️ Natural language encoding"admin token expires in 1h"
⏰ Time travel (--fake-time)✅ Deterministic testing
📋 Config as code (.jwt-cli.toml)✅ Profiles, defaults, keys
🎨 Premium terminal UX✅ Colors, boxes, tablesPartial
🔑 Key generation (RSA/EC/Ed25519)✅ JWK + PEM outputPartial
📦 Dual ESM + CJS outputN/A
🧪 Strict TypeScript, zero anyN/A

Installation

# Global install (recommended)
npm install -g jwt-lab

# Or use with npx
npx jwt-lab --help

# Or add to a project
npm install jwt-lab --save-dev

Quick Start

# Encode a JWT with HMAC secret
jwt encode '{"sub":"user1","role":"admin"}' --secret my-secret --exp 1h

# Decode without verification
jwt decode eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyMSJ9.xxx

# Verify signature + claims
jwt verify <token> --secret my-secret

# Security audit (no keys needed)
jwt explain <token>

# Inspect with full breakdown
jwt inspect <token> --secret my-secret

# Generate key pairs
jwt keygen ec --pem --out-dir ./keys

# Natural language encoding
jwt encode "admin token for user ali@example.com expires in 12h" --secret s

# Start MCP server for AI agents
jwt mcp serve --port 3000

Commands

jwt encode

Encode a JWT from JSON or natural language.

# JSON payload
jwt encode '{"sub":"user1","role":"admin","email":"user@example.com"}' \
  --secret my-secret \
  --exp 1h \
  --iss https://auth.myapp.com

# Natural language (no LLM — deterministic regex parser)
jwt encode "admin user user@example.com expires in 30m" --secret s

# With asymmetric key
jwt encode '{"sub":"svc"}' --key ./private.pem --alg ES256

# With profile from config
jwt encode '{"sub":"user1"}' --secret s --profile access_token

# Copy to clipboard
jwt encode '{"sub":"user1"}' --secret s --exp 1h --copy

# JSON output
jwt encode '{"sub":"user1"}' --secret s --json

Options:

FlagDescription
--secret <string>HMAC secret (HS256/384/512)
--key <path>PEM or JWK private key file
--alg <algorithm>Signing algorithm
--exp <duration>Expiration (e.g., 1h, 30m, 7d)
--iss <string>Issuer claim
--sub <string>Subject claim
--aud <string>Audience claim
--kid <string>Key ID in header
--jtiGenerate random UUID as JTI
--header <json>Additional header fields
--profile <name>Use named profile from config
--copyCopy token to clipboard
--jsonOutput as JSON

jwt decode

Decode a JWT without verification.

jwt decode <token>

# From stdin
echo "<token>" | jwt decode -

# Batch mode
cat tokens.txt | jwt decode - --batch

# JSON output
jwt decode <token> --json

jwt verify

Full signature verification and claims validation.

# HMAC
jwt verify <token> --secret my-secret

# Asymmetric key
jwt verify <token> --key ./public.pem --alg ES256

# JWKS endpoint
jwt verify <token> --jwks https://auth.example.com/.well-known/jwks.json

# Required claims
jwt verify <token> --secret s --require sub,iss,exp

# Clock skew tolerance
jwt verify <token> --secret s --leeway 30

# Time travel for testing
jwt verify <token> --secret s --fake-time 2024-01-01T00:00:00Z

Output:

✅ Valid JWT
Algorithm: HS256
Subject:   user1

jwt inspect

High-level token breakdown with status, metadata, and security posture.

jwt inspect <token>
jwt inspect <token> --secret my-secret  # with verification
jwt inspect <token> --json              # machine-readable
jwt inspect <token> --table             # table format

Output:

╭───── Token Inspection ──────╮
│                              │
│  Status: ✅ valid            │
│  Algorithm: HS256            │
│  Subject:   user1            │
│  Issuer:    auth.example.com │
│  Expires in: 59m 30s         │
│                              │
│  Lint Findings:              │
│  ⚠️ [pii-claims] Payload ... │
│                              │
╰──────────────────────────────╯

jwt explain

Static security audit — no keys required.

jwt explain <token>
jwt explain <token> --json    # for CI pipelines
jwt explain <token> --table   # table format

Output:

🔍 JWT Security Audit

❌ [none-algorithm] Token uses the "none" algorithm
  → Replace "none" with a secure algorithm such as RS256 or ES256

⚠️ [pii-claims] Payload contains claims that may hold PII: email
  → Avoid embedding PII directly in JWT payloads

ℹ️ [hmac-preferred-asymmetric] Token uses HMAC algorithm (HS256)
  → Consider using an asymmetric algorithm such as RS256 or ES256

Built-in security rules:

Rule IDSeverityWhat it checks
none-algorithm🔴 errorAlgorithm is "none"
missing-exp🟡 warnToken has no expiration
long-lived-token🟡 warnLifetime > 24 hours
pii-claims🟡 warnClaims containing PII patterns
missing-nbf-long-lived🔵 infoLong-lived token without nbf
hmac-preferred-asymmetric🔵 infoHMAC where asymmetric is preferred

jwt keygen

Generate cryptographic key pairs.

# EC key pair (default P-256)
jwt keygen ec

# RSA key pair
jwt keygen rsa --bits 4096

# Ed25519
jwt keygen ed25519

# PEM output to files
jwt keygen ec --pem --out-dir ./keys

# JWK with key ID
jwt keygen rsa --jwk --kid my-production-key

MCP Server

jwt-lab includes a full Model Context Protocol HTTP/JSON server for AI agents and programmatic access.

Start the server

jwt mcp serve --port 3000 --host 0.0.0.0

Endpoints

MethodPathDescription
POST/encodeEncode a JWT
POST/decodeDecode a JWT
POST/verifyVerify a JWT
POST/inspectInspect a JWT
POST/keygenGenerate key pair
POST/explainSecurity audit
GET/docsOpenAPI 3.1 spec
GET/healthHealth check

Examples with curl

# Encode
curl -X POST http://localhost:3000/encode \
  -H "Content-Type: application/json" \
  -d '{"payload":{"sub":"user1"},"secret":"my-secret","alg":"HS256","exp":"1h"}'

# Decode
curl -X POST http://localhost:3000/decode \
  -H "Content-Type: application/json" \
  -d '{"token":"eyJhbGciOiJIUzI1NiJ9..."}'

# Verify
curl -X POST http://localhost:3000/verify \
  -H "Content-Type: application/json" \
  -d '{"token":"eyJ...","secret":"my-secret"}'

# Explain (security audit)
curl -X POST http://localhost:3000/explain \
  -H "Content-Type: application/json" \
  -d '{"token":"eyJ..."}'

# Generate key pair
curl -X POST http://localhost:3000/keygen \
  -H "Content-Type: application/json" \
  -d '{"type":"ec","format":"jwk"}'

# OpenAPI docs
curl http://localhost:3000/docs

Authentication

Set the MCP_API_KEY environment variable to enable Bearer token authentication:

MCP_API_KEY=your-secret-key jwt mcp serve

# Then include the key in requests:
curl -X POST http://localhost:3000/encode \
  -H "Authorization: Bearer your-secret-key" \
  -H "Content-Type: application/json" \
  -d '{"payload":{"sub":"user1"},"secret":"s","alg":"HS256"}'

Security Features

  • Token redaction: Full tokens are never logged; truncated to 20 chars
  • Claim redaction: Configure mcp.redactClaims to hide sensitive claims in responses
  • Rate limiting: Sliding window per IP (configurable)
  • CORS: Configurable allowed origins
  • Input validation: All requests validated with Zod schemas

Configuration

Create a .jwt-cli.toml in your project root:

[defaults]
iss = "https://auth.myapp.com/"
aud = "myapp-api"
alg = "ES256"

[profiles.access_token]
ttl    = "15m"
scopes = ["read", "write"]

[profiles.service_token]
ttl = "1h"
aud = "internal-service"

[lint]
piiClaimPatterns = ["email", "phone", "ssn"]

[lint.severityOverrides]
"missing-exp" = "error"

[mcp]
port = 3000
redactClaims = ["email", "phone"]

[mcp.rateLimit]
windowSeconds = 60
maxRequests   = 100

The CLI auto-discovers .jwt-cli.toml by walking upward from the current directory. Use --config <path> to specify a custom path.

Priority: CLI flags > config file > built-in defaults


Global Flags

FlagDescription
--helpShow help
--versionShow version
--fake-time <iso8601>Override system clock
--config <path>Path to config file
--jsonMachine-readable JSON output

API Reference

Core Library

jwt-lab's core is a pure, I/O-free TypeScript library. All functions return Result<T, E> types — no exceptions thrown.

import { encodeToken, decodeToken, verifyToken, lintToken, generateKeyPair, parseDuration } from 'jwt-lab';

See src/core/ for full API documentation with TSDoc comments.


Tech Stack

CategoryChoice
LanguageTypeScript 6 (strict mode, zero any)
RuntimeNode.js ≥ 22
JWTjose v6
CLIcommander v14
Validationzod v4
HTTPhono + @hono/node-server
Buildtsup (dual ESM + CJS)
Testsvitest v4
Terminalpicocolors, boxen, ora, cli-table3

Shell Completions

jwt-lab ships with built-in tab-completion scripts for Bash, Zsh, and Fish. The completions are aware of every subcommand and flag — pressing Tab surfaces commands, options, algorithm names, and file paths in context.

How it works

The jwt completions <shell> command prints a shell-specific completion script to stdout. You either eval it at shell startup or write it to a file that your shell auto-loads. No third-party tools are required.

jwt completions bash   →  prints a Bash completion function + `complete -F` binding
jwt completions zsh    →  prints a Zsh `_jwt` compdef function
jwt completions fish   →  prints Fish `complete` directives

Bash

One-liner (current session only):

eval "$(jwt completions bash)"

Persistent — add to ~/.bashrc:

echo 'eval "$(jwt completions bash)"' >> ~/.bashrc
source ~/.bashrc

Or save to the system completions directory (recommended for shared machines):

jwt completions bash | sudo tee /etc/bash_completion.d/jwt > /dev/null

Requires bash-completion package. Install with brew install bash-completion on macOS or apt install bash-completion on Debian/Ubuntu.

Zsh

One-liner (current session only):

eval "$(jwt completions zsh)"

Persistent — add to ~/.zshrc:

echo 'eval "$(jwt completions zsh)"' >> ~/.zshrc
source ~/.zshrc

Or save to a $fpath directory (the clean approach):

# Pick any directory already in your fpath, or create one
mkdir -p ~/.zsh/completions
jwt completions zsh > ~/.zsh/completions/_jwt

# Make sure the directory is in fpath — add to ~/.zshrc if not already there:
echo 'fpath=(~/.zsh/completions $fpath)' >> ~/.zshrc
echo 'autoload -Uz compinit && compinit' >> ~/.zshrc
source ~/.zshrc

oh-my-zsh users: Save to ~/.oh-my-zsh/completions/_jwt — it's already in fpath.

Fish

Fish completions are discovered automatically from ~/.config/fish/completions/. Just save the script there:

jwt completions fish > ~/.config/fish/completions/jwt.fish

Completions take effect immediately — no source or restart needed.

What gets completed

ContextCompletions offered
jwt <Tab>All subcommands with descriptions
jwt encode <Tab>--secret, --key, --alg, --exp, --iss, --json, …
jwt verify <Tab>--secret, --key, --jwks, --oidc-discovery, --alg, --require, --leeway, …
jwt keygen <Tab>Algorithm types: RS256 RS384 RS512 ES256 ES384 ES512 EdDSA PS256 PS384 PS512
jwt --alg <Tab>Full algorithm list
--key <Tab>File path completion (all shells)
--config <Tab>File path completion (all shells)
jwt completions <Tab>bash zsh fish

CI/CD & Publishing

Every push and pull request to main runs the full pipeline:

JobSteps
Test & Buildlint → type-check → tests → build → CLI smoke test (Node 22 & 24)
Security Auditnpm audit at moderate severity
CodeQLStatic analysis for JavaScript/TypeScript (separate scheduled workflow)
PublishRuns only on v* tag push → bumps version → builds → publishes to npm with provenance

Publishing a release

# 1. Bump version
npm version 1.0.0 --no-git-tag-version
git add package.json && git commit -m "chore: bump version to 1.0.0"
git push origin main

# 2. Create a GPG-signed annotated tag
git tag -s v1.0.0 -m "Release v1.0.0"
git tag -v v1.0.0  # verify signature

# 3. Push tag — triggers the publish workflow
git push origin v1.0.0

# 4. Create a signed GitHub Release
gh release create v1.0.0 --title "v1.0.0" --notes "Release notes here" --verify-tag

For a prerelease (e.g. v1.1.0-beta.1), the package is published with the beta dist-tag automatically.

The workflow uses npm publish --provenance, which attaches a cryptographic SLSA Level 2 attestation proving the package was built from this exact commit.

Required repository secret

SecretDescription
NPM_TOKENnpm Automation token with publish access — add at Settings → Secrets → Actions
# Install dependencies
npm install

# Build
npm run build

# Run tests
npm test

# Type check
npm run type-check

# Lint
npm run lint

# Start MCP server (dev)
npm run dev:mcp

CI/CD & Publishing

Every push and pull request to main runs the full pipeline:

JobSteps
Test & Buildlint → type-check → tests → build → CLI smoke test (Node 22 & 24)
Security Auditnpm audit at moderate severity
CodeQLStatic analysis for JavaScript/TypeScript (separate scheduled workflow)
PublishRuns only on GitHub Release publish → bumps version → builds → publishes to npm

Publishing a release

  1. Create and push a tag: git tag v1.0.0 && git push origin v1.0.0
  2. Create a GitHub Release from that tag (set it to Published, not Draft)
  3. The pipeline auto-bumps package.json, builds, and publishes to npm

For a prerelease (e.g. v1.1.0-beta.1), the package is published with the beta dist-tag automatically.

Required repository secret

SecretDescription
NPM_TOKENnpm automation token with publish access — add in Settings → Secrets → Actions

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT © jwt-lab contributors


Built with ❤️ for developers and AI agents

Report Bug · Request Feature · Discussions

Reviews

No reviews yet

Sign in to write a review