Skip to content

Repository files navigation

MCP Release

npm version npm downloads

MCP Release validates MCP servers. It verifies the protocol handshake, discovers tools, validates their schemas, and checks for stdio protocol violations. Supports both HTTP/SSE remote servers and local stdio servers (spawned processes). It does not execute tools or accept credentials.

Web app: https://mcprelease.dev Demo endpoint: https://mcp-release-fixture.vercel.app/mcp Repository: https://github.com/daranium2020/mcp-release


Quick start

Browser

The web checker at https://mcprelease.dev supports public HTTPS endpoints only. It does not accept credentials and cannot reach private networks or localhost.

  1. Open https://mcprelease.dev
  2. Enter a public HTTPS MCP endpoint URL and click Run Release Check
  3. Review the report. Download it as JSON or Markdown if needed.

To try without a real server, paste https://mcp-release-fixture.vercel.app/mcp directly.

CLI

The CLI runs on your machine and supports public, private, localhost, and authenticated MCP endpoints. Credentials stay in your environment and are never stored.

The web checker at https://mcprelease.dev only accepts public HTTPS endpoints without credentials. Use the CLI or GitHub Action for private networks, localhost, staging servers, or any endpoint that requires authentication.

# Install globally
npm install -g @mcp-release/cli

# Or run directly without installing
npx -y @mcp-release/cli check https://your-mcp-server.example.com/mcp

HTTP/SSE endpoints:

# Public endpoint
mcp-release check https://your-mcp-server.example.com/mcp

# JSON output
mcp-release check https://example.com/mcp --json

# Markdown output
mcp-release check https://example.com/mcp --markdown

# Write report to file
mcp-release check https://example.com/mcp --json --out report.json

# Bearer token from environment variable (recommended for secrets)
MCP_TOKEN=your-token mcp-release check https://staging.example.com/mcp --bearer-token-env MCP_TOKEN

# Literal header (for non-secret values only)
mcp-release check https://staging.example.com/mcp --header "X-Tenant-Id: acme"

# Header value from environment variable
MY_KEY=secret mcp-release check https://staging.example.com/mcp --header-env "X-API-Key=MY_KEY"

# Localhost or private network endpoint
mcp-release check http://localhost:4000/mcp --allow-http

# Exit 4 on WARNING (e.g., AUTH_REQUIRED)
MCP_TOKEN=your-token mcp-release check https://staging.example.com/mcp \
  --bearer-token-env MCP_TOKEN --fail-on-warning

Local stdio servers (new in v0.2.0):

# Spawn a local server by command string; validate over stdin/stdout
mcp-release check --stdio --command "npx -y my-mcp-server"

# With a working directory
mcp-release check --stdio --command "node dist/server.js" --cwd /path/to/project

# JSON output
mcp-release check --stdio --command "node dist/server.js" --json

# Write report to file
mcp-release check --stdio --command "npx -y my-mcp-server" --json --out report.json

Stdio validation runs entirely locally. No data is sent to the web app or any remote service.

From this repo (without installing):

pnpm build
node packages/cli/dist/index.js check https://mcp-release-fixture.vercel.app/mcp

CLI exit codes:

Code Meaning
0 PASS
1 FAIL (validation found a blocking issue)
2 Invalid CLI usage (bad arguments, missing env var)
3 Transport error, timeout, or unexpected runtime error
4 WARNING and --fail-on-warning was set

All CLI options for check:

Option Description
--stdio Validate a local stdio server (spawned process). Requires --command.
--command <cmd> Command string to spawn the stdio server (e.g. "npx -y my-mcp-server").
--cwd <dir> Working directory for the spawned process (stdio mode only).
--header "Name: value" Add a request header (HTTP mode). Repeatable.
--header-env "Name=VAR" Read a header value from an env var (HTTP mode). Repeatable.
--bearer-token-env VAR Read a bearer token from an env var; sends Authorization: Bearer <token> (HTTP mode).
--timeout-ms <ms> Request/startup timeout in milliseconds (default: 10000).
--max-redirects <n> Maximum redirects to follow (HTTP mode, default: 3).
--allow-http Allow HTTP connections for localhost/development (HTTP mode).
--json Print JSON report to stdout.
--markdown Print Markdown report to stdout.
--out <path> Write report to file (JSON by default; Markdown if --markdown).
--config <path> Path to a mcp-release.config.yml file. Runs all scenarios defined in the file.
--fail-on-warning Exit 4 when overall status is WARNING.

Configuration file (v0.3.0)

The --config flag runs a sequence of named scenarios against a single MCP endpoint, each with independent headers, expected outcomes, retry settings, and timeouts. This is the recommended approach for authenticated endpoints and regression testing.

Config file format (mcp-release.config.yml):

version: 1
endpoint: https://api.example.com/mcp

# Base headers sent with all requests. ${VAR} is resolved from environment at runtime.
headers:
  Authorization: Bearer ${API_TOKEN}

timeouts:
  connectMs: 5000    # TCP connection timeout (default: 10000)
  responseMs: 10000  # HTTP response timeout, separate from connect (default: timeoutMs)

retries:
  maxAttempts: 3     # Total attempts including first (default: 1 = no retry)
  backoffMs: 500     # Fixed wait between retries (default: 1000)
  retryOn:           # Categories to retry — all disabled by default
    - rate-limit         # HTTP 429
    - server-error       # 5xx responses
    - connection-failure # Connect failures and connect timeouts
    - response-timeout   # Response timeouts

scenarios:
  # Expected positive: server responds correctly with provided token
  - name: healthy
    expect:
      result: pass

  # Expected negative: calling without token should return 401
  - name: missing-auth
    removeHeaders:
      - Authorization
    expect:
      httpStatus: 401

  # Custom token override: per-scenario header replacement
  - name: custom-token
    headers:
      Authorization: Bearer ${READONLY_TOKEN}
    expect:
      result: pass

Environment variable substitution:

Header values use ${VAR_NAME} syntax. Variables are resolved at runtime from the process environment, never at parse time. Unresolved variables throw an error before any requests are made. Resolved values are never printed or logged.

API_TOKEN=your-token READONLY_TOKEN=read-only-token \
  mcp-release check --config mcp-release.config.yml

Report formats for config runs:

mcp-release check --config mcp-release.config.yml              # terminal (default)
mcp-release check --config mcp-release.config.yml --json       # JSON
mcp-release check --config mcp-release.config.yml --markdown   # Markdown
mcp-release check --config mcp-release.config.yml --markdown --out report.md

GitHub Actions with config file:

- name: Validate MCP server
  run: |
    mcp-release check --config mcp-release.config.yml --markdown >> $GITHUB_STEP_SUMMARY
  env:
    API_TOKEN: ${{ secrets.API_TOKEN }}
    READONLY_TOKEN: ${{ secrets.READONLY_TOKEN }}

GitHub Action

The GitHub Action supports public, staging, and authenticated MCP endpoints. Secrets stay in GitHub Actions secrets and are masked in logs.

Reference by a tagged release or by commit SHA for pinned usage:

HTTP/SSE endpoint:

- name: Validate MCP server
  uses: daranium2020/mcp-release@v0.3.0
  with:
    endpoint: https://staging.example.com/mcp
    bearer-token-env: MCP_TOKEN   # reads token from env; never put secrets inline
    fail-on: fail                 # optional: fail (default) | warning
    timeout-ms: 10000             # optional
    format: markdown              # optional: json | markdown | both
  env:
    MCP_TOKEN: ${{ secrets.MCP_TOKEN }}

Local stdio server (new in v0.2.0):

- name: Validate local MCP server
  uses: daranium2020/mcp-release@v0.3.0
  with:
    transport: stdio
    command: npx -y my-mcp-server
    working-directory: ./packages/my-server   # optional
    fail-on: fail
    format: markdown

Pass all secrets through the env block using GitHub Actions secrets (${{ secrets.YOUR_SECRET }}). Never put secret values directly in with: inputs or workflow YAML.

All inputs:

Input Description
transport http (default) or stdio.
endpoint MCP endpoint URL (required when transport: http).
command Command to spawn the stdio server (required when transport: stdio).
working-directory Working directory for the spawned process (stdio mode).
bearer-token-env Name of an env var containing a bearer token. Sends Authorization: Bearer <token> (HTTP mode).
header Newline-separated Name: value pairs added to every request (HTTP mode).
header-env Newline-separated Name=ENV_VAR pairs. Values read from environment (HTTP mode).
fail-on fail (default) or warning.
timeout-ms Request/startup timeout in milliseconds.
format json, markdown, or both.

Outputs: status, failures, warnings, tools, report-path, pass-count, warning-count, fail-count, report-json, report-markdown

The action annotates the workflow job with findings and writes a summary to the GitHub Actions job summary.


What is checked

Area Details
Protocol MCP initialization handshake, protocol version negotiation, transport response codes
Tool schemas Tool names (non-empty, valid characters), descriptions, inputSchema (valid JSON Schema, Ajv-compilable), outputSchema (if present), duplicate names
Network safety (HTTP) SSRF protection, DNS pinning, redirect chain validation (up to 3 hops), HTTPS enforcement across all redirects
Stdio transport (stdio) Non-JSON lines on stdout (STDIO_UNEXPECTED_OUTPUT), malformed MCP messages (STDIO_FRAMING_ERROR), response size limit (STDIO_RESPONSE_SIZE_EXCEEDED), unclean shutdown (STDIO_SHUTDOWN_TIMEOUT)
Auth & resilience (v0.3.0) 401/403 classification, expired vs invalid credential detection, 429 rate-limit with Retry-After, connect/response timeout types, configurable retries, scenario-based expected-negative testing
Reports Findings exportable as JSON or Markdown

What is not checked: tools are never invoked, runtime correctness of tool responses is not assessed.


Result meanings

Result Meaning
PASS All checks completed without a blocking or incomplete condition. A PASS does not guarantee universal security or correctness. It reflects the checks MCP Release ran.
WARNING One or more checks could not complete or found a non-blocking issue. Example: AUTH_REQUIRED (server returned 401, no credentials provided).
FAIL One or more checks found a blocking condition. Examples: AUTH_INVALID, AUTH_EXPIRED, AUTH_FORBIDDEN, RATE_LIMITED, CONNECT_TIMEOUT, RESPONSE_TIMEOUT.

Auth finding codes (v0.3.0)

Code Severity Condition
AUTH_REQUIRED WARNING 401 response without credentials — server requires auth
AUTH_INVALID FAIL 401 response with credentials (including RFC 6750 error="invalid_token")
AUTH_EXPIRED FAIL 401 response with credentials + explicit unambiguous expiry code: error="expired" or error="token_expired"
AUTH_FORBIDDEN FAIL 403 response (insufficient permissions)
SCENARIO_MISMATCH FAIL Actual outcome did not match the declared expect block

Limitation: AUTH_EXPIRED is only produced when the server returns an unambiguous, expiry-specific WWW-Authenticate: Bearer error= value. The RFC 6750 standard code error="invalid_token" is classified as AUTH_INVALID because it covers expired, revoked, and malformed tokens — it is too broad to unambiguously signal expiry. Most OAuth 2.0 servers use invalid_token and will produce AUTH_INVALID. Response bodies and error_description fields are never read or included in reports.

Resilience finding codes (v0.3.0)

Code Severity Condition
RATE_LIMITED FAIL 429 Too Many Requests
RETRY_AFTER_INVALID WARNING 429 with an unparseable Retry-After header
RETRY_EXHAUSTED FAIL All configured attempts failed; or Retry-After exceeds the 60s cap
CONNECT_TIMEOUT FAIL TCP connection timed out before establishment
RESPONSE_TIMEOUT FAIL Connection established but server did not send a response in time
SCENARIO_TIMEOUT FAIL Scenario exceeded its per-scenario time budget (never retried)

Retry rules (v0.3.0)

Retries are off by default. Each failure category must be explicitly listed in retries.retryOn. A retry only happens when retries.maxAttempts > 1 AND the relevant category is enabled.

Condition retryOn category Default
HTTP 429 (RATE_LIMITED) rate-limit off
5xx responses server-error off
Connection failures and connect timeouts connection-failure off
Response timeouts response-timeout off
401, 403 Never retried
400, schema errors, malformed MCP Never retried
Scenario timeout Never retried

Rate-limit retries honour the server's Retry-After header (integer seconds or HTTP date), capped at 60 seconds. Values above the cap produce RETRY_EXHAUSTED immediately without waiting.


Security model

  • Tools are discovered via tools/list but never invoked. No arguments are constructed or sent.
  • Only public HTTPS endpoints are accepted by the web checker. HTTP is rejected before any connection.
  • Private, loopback, link-local (169.254.0.0/16), and cloud-metadata (169.254.169.254) destinations are blocked at the DNS level (web). CLI/GitHub Action allow private networks for local testing.
  • DNS pinning closes the TOCTOU gap. The resolved IP is pinned at connection time.
  • Redirects are re-validated at each hop. HTTPS applies across all redirects.
  • Remote response bodies are never read or included in findings. Only structured response headers (e.g. WWW-Authenticate error=, Retry-After) are inspected, never body content.
  • The web checker accepts no credentials. The CLI and GitHub Action send credentials only to the configured MCP endpoint — never to MCP Release infrastructure.
  • TLS verification is enforced (rejectUnauthorized: true).
  • Error messages are redacted. Token patterns and embedded URL credentials are stripped before being returned.
  • Sensitive headers (Authorization, x-api-key, cookie, etc.) are dropped on cross-origin redirects.
  • ${VAR_NAME} env var values resolved from config headers are never logged or printed.

Report formats

JSON (schemaVersion: "1")

// HTTP/SSE endpoint
{
  "schemaVersion": "1",
  "serverUrl": "https://example.com/mcp",
  "checkedAt": "2026-06-28T00:00:00.000Z",
  "durationMs": 234,
  "overallStatus": "PASS",
  "transport": { "httpStatus": 200, "durationMs": 120, "redirectCount": 0 },
  "protocolVersion": "1.0.0",
  "serverInfo": { "name": "my-server", "version": "1.0.0" },
  "findings": [
    { "code": "INIT_OK", "severity": "PASS", "message": "MCP initialization succeeded" }
  ],
  "tools": [
    { "name": "my_tool", "overallStatus": "PASS", "findings": [...] }
  ]
}

// Stdio server — transport is null (N/A for spawned process)
{
  "schemaVersion": "1",
  "serverUrl": "stdio:node",
  "checkedAt": "2026-06-28T00:00:00.000Z",
  "durationMs": 412,
  "overallStatus": "PASS",
  "transport": null,
  "protocolVersion": "1.0.0",
  "serverInfo": { "name": "my-server", "version": "1.0.0" },
  "findings": [
    { "code": "INIT_OK", "severity": "PASS", "message": "MCP initialization succeeded" }
  ],
  "tools": [
    { "name": "my_tool", "overallStatus": "PASS", "findings": [...] }
  ]
}

Markdown

Human-readable summary suitable for pull request descriptions and release notes. Generated by packages/reporter.

Browser UI

After a check completes, the results area displays overall status, findings grouped by severity, discovered tools, and export buttons (Copy JSON, Download JSON, Download Markdown).


Workspace

Package / App Purpose
packages/core Validation engine: SSRF guard, DNS pinning, transport adapter, MCP validator, report model
packages/cli Command-line interface
packages/reporter JSON, Markdown, and terminal report formatters
packages/github-action GitHub Action (action.yml) wrapping the core validator
apps/web Next.js 15 web interface (https://mcprelease.dev)
apps/public-mcp-fixture Public MCP fixture server (https://mcp-release-fixture.vercel.app/mcp)
fixtures/servers Localhost fixture MCP servers used in tests

Deployment

Two independent Vercel projects:

Project Root directory URL
mcp-release apps/web https://mcprelease.dev
mcp-release-fixture apps/public-mcp-fixture https://mcp-release-fixture.vercel.app

apps/web (mcprelease.dev) deploys automatically on every push to main. apps/public-mcp-fixture has automatic deployments disabled and is deployed manually.

apps/web depends on packages/core and packages/reporter. Its build command builds those packages first:

pnpm --filter @mcp-release/core build && pnpm --filter @mcp-release/reporter build && pnpm --filter @mcp-release/web build

apps/public-mcp-fixture has no sibling package dependencies and builds with next build directly.


Local development

Requirements: Node.js ≥ 22.13.0, pnpm ≥ 10.28.0

pnpm install                  # install all workspace dependencies
pnpm typecheck                # TypeScript type check (all packages + apps)
pnpm lint                     # ESLint across the workspace
pnpm build                    # build all packages and apps
pnpm test                     # run all tests (500+ automated tests)

Start the web app

pnpm --filter @mcp-release/web dev
# → http://localhost:3000

The development server shows fixture buttons (PASS / WARNING / FAIL) that load sample reports without making network requests. These buttons are removed in production builds.

Web app structure

apps/web/src/
├── app/
│   ├── layout.tsx              HTML shell, metadata, OG tags
│   ├── page.tsx                Landing page
│   ├── docs/
│   │   └── page.tsx            Documentation page
│   └── api/check/
│       ├── handler.ts          Testable request handler
│       └── route.ts            Next.js route entry point
├── components/
│   ├── Header.tsx / .module.css
│   ├── Footer.tsx / .module.css
│   ├── CheckClient.tsx / .module.css   Client component (form, state)
│   └── Results.tsx / .module.css       Report display
└── lib/
    ├── constants.ts            SITE_URL, GITHUB_URL, DEMO_ENDPOINT, timeout bounds
    ├── rate-limit.ts           In-memory sliding-window rate limiter
    └── concurrency.ts          In-memory concurrency guard

API contract

POST /api/check accepts application/json:

{ "endpoint": "https://example.com/mcp", "timeoutMs": 10000 }
  • endpoint: required, HTTPS only, no embedded credentials
  • timeoutMs: optional, 1000-30000 ms (default 10000)
  • Unexpected fields are rejected with 400 UNEXPECTED_FIELD

Returns 200 { "report": CheckReport } or a JSON error body with error and message.

Abuse controls (in-memory)

  • Rate limit: 10 requests per IP per minute (sliding window)
  • Concurrency: max 5 simultaneous outbound checks

Both controls are per-process. See Known Limitations below.


Known limitations

  • Public HTTPS endpoints only. HTTP and private network endpoints are rejected.
  • No credential input. Authenticated checks are not performed. Servers requiring authorization return AUTH_REQUIRED (WARNING).
  • Tools are not invoked. Runtime correctness of tool responses is not validated.
  • A PASS is not a security guarantee. It reflects the checks MCP Release ran. Runtime behavior may differ in other environments.
  • In-memory rate limiting. Per-process only, not shared across horizontally-scaled instances.
  • Reports are not stored server-side. Export before closing the tab.
  • x-forwarded-for is used for rate limiting. Accurate behind a trusted proxy, not verified otherwise.

Contributing

pnpm install
pnpm typecheck && pnpm lint && pnpm test

All PRs run the full suite via .github/workflows/ci.yml. The workflow uses SHA-pinned actions and pnpm 10.28.0.

Core validation behavior (transport, SSRF, DNS pinning, error classification, report format) is covered by tests in packages/core/tests/. Web UI behavior is covered by tests in apps/web/tests/.

About

Release validation and CI platform for production MCP servers

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages