A read-only viewer for structured evidence bundles. AI agents and CI pipelines create zip bundles containing test results, reports, and artifacts — Evidence Browser renders them in a browsable, shareable interface.
- Bundle viewer — Browse zip bundles with file tree navigation
- Rich rendering — Markdown (with embedded images), syntax-highlighted code, image preview
- Pluggable storage — Local filesystem or S3/R2-compatible object storage
- Authentication — Built-in username/password session auth for admin and API access
- AI Agent integration —
/llm.txtendpoint and MCP server for programmatic access - Hierarchical bundle IDs —
org/repo/pr-42/run-1maps to nested storage paths
Run the published image with a random session secret and local bundle storage:
docker run -p 3000:3000 \
-e AUTH_SECRET="$(openssl rand -base64 32)" \
-v "$PWD/data:/data" \
ghcr.io/hojinzs/evidence-browser:latestOpen http://localhost:3000 for the web app.
AUTH_SECRET signs admin sessions and must be a real random value for any
shared or production instance.
# Install dependencies
npm install
# Copy and configure environment
cp .env.example .env.local
# Start API + web development servers
npm run devOpen http://localhost:3000 for the web app.
In a workspace, click Load demo bundle or run eb bundle upload examples/sample.zip -w default to render the bundled sample immediately.
- Web dev server:
http://localhost:3000(Vite) - API dev server:
http://localhost:3001(proxied as/apifrom the web app)
| Variable | Default | Description |
|---|---|---|
AUTH_SECRET |
evidence-browser-default-secret-change-me |
Session signing secret (must be explicitly set in production) |
AUTH_BYPASS |
false |
Set to true only for trusted local or intranet deployments. All requests run as an admin user, /setup is skipped, and no login/API key is required. Do not expose an instance with this enabled to an untrusted network. |
| Variable | Default | Description |
|---|---|---|
STORAGE_TYPE |
local |
local or s3 |
STORAGE_LOCAL_PATH |
— | Directory path (required when local) |
S3_BUCKET |
— | Bucket name (required when s3) |
S3_REGION |
auto |
AWS region or auto for R2 |
S3_ENDPOINT |
— | Custom endpoint (e.g. R2: https://<account>.r2.cloudflarestorage.com) |
S3_ACCESS_KEY_ID |
— | S3 access key |
S3_SECRET_ACCESS_KEY |
— | S3 secret key |
S3_FORCE_PATH_STYLE |
false |
Use path-style URLs (for MinIO, etc.) |
| Variable | Default | Description |
|---|---|---|
MAX_BUNDLE_SIZE |
524288000 (500 MB) |
Maximum zip file size |
MAX_FILE_COUNT |
10000 |
Maximum files per bundle |
MAX_SINGLE_FILE_SIZE |
104857600 (100 MB) |
Maximum single file size |
CACHE_TTL_MS |
1800000 (30 min) |
In-memory cache TTL |
CACHE_MAX_ENTRIES |
50 |
LRU cache capacity |
| Variable | Default | Description |
|---|---|---|
MCP_API_KEY |
— | Optional Bearer token for /api/mcp auth. If unset, the endpoint is public. |
A bundle is a zip file with a required manifest.json:
my-bundle.zip
├── manifest.json
├── index.md # landing page (referenced by manifest)
├── logs/
│ └── output.log
└── screenshots/
└── step-1.png
{
"version": 1,
"title": "PR #42 — Test Results",
"index": "index.md"
}| Field | Type | Required | Description |
|---|---|---|---|
version |
number | yes | Bundle format version (use 1) |
title |
string | yes | Displayed as the page title |
index |
string | yes | Relative path to the landing file |
Additional fields are allowed and passed through.
Derived from the zip filename (without .zip). Supports hierarchical paths:
org/repo/pr-42/run-1 → stored as org/repo/pr-42/run-1.zip
STORAGE_TYPE=local
STORAGE_LOCAL_PATH=./data/bundlesPlace bundles at {STORAGE_LOCAL_PATH}/{bundleId}.zip.
STORAGE_TYPE=s3
S3_BUCKET=evidence-bundles
S3_REGION=auto
S3_ENDPOINT=https://<account>.r2.cloudflarestorage.com
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...Upload bundles with key {bundleId}.zip.
GET /llm.txt
Returns a plain-text guide describing the bundle format, storage configuration, upload instructions, size limits, and available API endpoints. Designed for LLM consumption (similar to robots.txt).
Evidence Browser exposes an MCP server via Streamable HTTP:
POST /api/mcp
Accept: application/json, text/event-stream
Authorization: Bearer <MCP_API_KEY> # only if MCP_API_KEY is set
Available tools:
| Tool | Description |
|---|---|
get_bundle_schema |
Returns manifest.json schema and zip structure |
get_storage_info |
Returns storage type, bucket, endpoint, region (no secrets) |
get_upload_instructions |
Step-by-step upload instructions for the current storage |
list_bundles |
Lists available bundle IDs with optional prefix filter |
Test with MCP Inspector:
npx @modelcontextprotocol/inspector http://localhost:3000/api/mcpAll endpoints require authentication unless AUTH_BYPASS=true is explicitly enabled. AUTH_BYPASS=true is intended only for trusted local or intranet deployments: it skips /setup, treats every request as admin, and logs a startup warning. Do not expose a bypass-enabled instance to an untrusted network.
Bundle routes are scoped to a workspace. Use the workspace slug for {ws}; {bundleId} is a single URL path segment, so encode reserved characters, including slashes as %2F, when constructing URLs.
| Method | Path | Description |
|---|---|---|
GET |
/w/{ws} |
Workspace bundle list page |
GET |
/w/{ws}/b/{bundleId} |
Bundle landing page |
GET |
/w/{ws}/b/{bundleId}/f?path={filePath} |
File viewer |
GET |
/api/w |
List workspaces (JSON) |
GET |
/api/w/{ws} |
Workspace details (JSON) |
GET |
/api/w/{ws}/bundle |
List workspace bundles (JSON) |
POST |
/api/w/{ws}/bundle |
Upload a bundle |
GET |
/api/w/{ws}/bundles/{bundleId}/meta |
Bundle manifest + file tree (JSON) |
GET |
/api/w/{ws}/bundles/{bundleId}/tree |
File tree only (JSON) |
GET |
/api/w/{ws}/bundles/{bundleId}/file?path={filePath} |
Raw file content |
GET |
/api/w/{ws}/bundles/{bundleId}/preview?path={htmlFilePath} |
Sandboxed HTML preview |
GET |
/llm.txt |
LLM integration guide (plain text) |
POST |
/api/mcp |
MCP Streamable HTTP endpoint |
AUTH_SECRET="$(openssl rand -base64 32)" docker compose upThe compose file builds the local Docker image, maps 3000:3000, and stores
bundles under ./data on the host. AUTH_SECRET is required; generate a fresh
random value instead of committing or reusing a fixed value.
For a local Node production build without Docker:
npm run build
npm run startnpm run build compiles:
packages/shared(shared types/utilities)packages/api(Hono API server)packages/web(Vite SPA), then copies it to rootweb/for static serving by the API runtime
- Hono (Node server + API routes)
- React 19
- Vite 8 (web app build/dev server)
- Tailwind CSS 4
- Shiki (Syntax highlighting)
- MCP SDK (AI agent integration)
This project is licensed under the MIT License. See LICENSE for details.