Command Palette

Search for a command to run...

UnylyUnyly
Browse all

Mock Server

FreeNot checked

A test MCP server that validates gateway connectivity, identity propagation, and provides mock tools for testing purposes.

GitHubEmbed

About

A test MCP server that validates gateway connectivity, identity propagation, and provides mock tools for testing purposes.

README

A test MCP server that runs behind mcp-auth-gateway. It exists to validate the platform plumbing — not to be a real product.

Purpose

This server is used to verify:

  1. gateway → backend MCP server connectivity
  2. path-based /mock/mcp routing
  3. gateway-signed X-MCP-Identity verification
  4. propagation of the user loginid
  5. the Streamable HTTP MCP transport
  6. GitOps / Kubernetes deployment

How it fits in

client ──TLS──> mcp-auth-gateway ──/mock/mcp──> mock-mcp-server (this repo)
                (Keycloak / OAuth,              (verifies X-MCP-Identity,
                 mints X-MCP-Identity)           runs MCP tools)
  • It runs only behind the gateway. External clients reach it exclusively via https://gateway.mcp.aidev.samsungds.net/mock/mcp.
  • It does NOT do Keycloak / OAuth verification itself. All external auth is the gateway's job.
  • It DOES verify the gateway's internal X-MCP-Identity JWT on /mcp and every slash-delimited path below it, rejecting anything missing or invalid with 401.
  • Tools read the caller's identity — especially loginid — through a shared get_current_user() helper; they never parse HTTP headers directly.

Identity: X-MCP-Identity

The gateway injects a signed internal JWT:

X-MCP-Identity: <gateway-signed-internal-jwt>

Example payload:

{
  "iss": "mcp-auth-gateway",
  "aud": "mock-mcp-server",
  "sub": "keycloak-user-sub",
  "loginid": "user.loginid",
  "username": "user.name",
  "email": "[email protected]",
  "groups": ["engineering"],
  "scopes": ["mcp:mock:use"],
  "request_id": "01J...",
  "iat": 1730000000,
  "nbf": 1730000000,
  "exp": 1730000060
}

Verified on every /mcp request:

  • JWT signature
  • iss == mcp-auth-gateway
  • aud == mock-mcp-server
  • integer iat, nbf, and exp, with five seconds of clock skew
  • exp > iat, exp >= nbf, and a maximum lifetime of five minutes
  • canonical, non-empty sub, loginid, and optional scalar/list values
  • exactly one non-empty X-MCP-Identity header (duplicates and combined values are rejected)

The MVP uses shared-secret HS256 via MCP_IDENTITY_JWT_SECRET; the secret must contain at least 32 bytes. The verifier lives behind a small abstraction (identity.IdentityVerifier) so a production RS256 / JWKS implementation can be dropped in without touching tools or middleware.

loginid usage

loginid is the primary per-user key. Tools use it to build deterministic, user-scoped mock data (mock_profile, mock_search) and to echo the caller's identity (echo, whoami).

Endpoints

Method Path Auth Purpose
GET /healthz none liveness
GET /readyz none readiness
ANY /mcp[/...] X-MCP-Identity (JWT) Streamable HTTP MCP endpoint

Requests to /mcp or /mcp/... without a valid X-MCP-Identity are rejected at the HTTP layer with 401 Unauthorized before reaching any tool. Similar names such as /mcpish are not part of this authentication boundary.

MCP tools

Tool Input Returns
whoami full identity asserted by the gateway
echo message: str { loginid, message }
mock_profile deterministic mock profile derived from loginid
mock_search query: str deterministic, user-scoped mock search results

Configuration

All configuration is via environment variables.

Variable Default Notes
APP_HOST 0.0.0.0
APP_PORT 8080 container port
MCP_ENDPOINT /mcp Streamable HTTP path
MCP_IDENTITY_ISSUER mcp-auth-gateway required iss
MCP_IDENTITY_AUDIENCE mock-mcp-server required aud
MCP_IDENTITY_ALGORITHM HS256 verifier algorithm
MCP_IDENTITY_JWT_SECRET required, at least 32 bytes unless dev mode is on
MCP_IDENTITY_DEV_MODE false if true, unauthenticated calls get a dummy user
LOG_LEVEL info
  • MCP_IDENTITY_JWT_SECRET is mandatory in production; the server refuses to start if it is missing or shorter than 32 bytes.
  • MCP_IDENTITY_DEV_MODE=true is only for local development. It defaults to false and must never be enabled in the cluster.

Local development

Requirements: Python ≥ 3.11 and uv.

# 1. install
uv sync --locked --extra dev

# 2. run the server
export MCP_IDENTITY_JWT_SECRET=dev-only-secret-that-is-at-least-32-bytes
export MCP_IDENTITY_DEV_MODE=false
python -m mock_mcp_server.main

Generate a dev identity token

Without the gateway you must supply your own X-MCP-Identity JWT:

export MCP_IDENTITY_JWT_SECRET=dev-only-secret-that-is-at-least-32-bytes

python scripts/make-dev-identity-token.py \
  --loginid test.user \
  --subject test-sub \
  --aud mock-mcp-server
# ->  export X_MCP_IDENTITY='<jwt>'

eval "$(python scripts/make-dev-identity-token.py --loginid test.user --subject test-sub)"

Test with curl

/mcp speaks Streamable HTTP (JSON-RPC). In stateless JSON mode you can call a tool directly:

# health (no auth)
curl -s localhost:8080/healthz

# whoami (requires identity)
curl -s localhost:8080/mcp \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -H "X-MCP-Identity: ${X_MCP_IDENTITY}" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"whoami","arguments":{}}}'

# missing identity -> 401
curl -i -s localhost:8080/mcp \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

Test with MCP Inspector

Use the integrated gateway/Keycloak workflow from the sibling GitOps repository rather than connecting Inspector directly to this internal backend:

cd ../mcp-platform-gitops
scripts/local-build-push.sh
scripts/local-up.sh
scripts/local-inspector.sh

The launcher pins @modelcontextprotocol/[email protected]. In its OAuth settings use client ID mcp-inspector, no client secret, scope openid mcp:mock:use, and the preselected Streamable HTTP gateway URL http://gateway.localhost:8080/mock/mcp.

Run the tests

uv run pytest        # or: .venv/bin/python -m pytest

Docker

Image name (pushed to cr.aidev.samsungds.net/mcp-platform):

cr.aidev.samsungds.net/mcp-platform/mock-mcp-server:<commit-sha>

Build & push:

REGISTRY=cr.aidev.samsungds.net \
IMAGE_TAG="$(git rev-parse HEAD)" \
CA_CERT_FILE=/path/to/system-ca.pem \
PUSH=1 \
scripts/build-and-push.sh

The image installs production dependencies from the checked-in uv.lock with frozen semantics, runs as a non-root user, exposes 8080, and ships a HEALTHCHECK against /readyz. Tag images by commit SHA (no latest in the cluster) so GitOps pins an immutable deployment reference.

Deployment (mcp-platform-gitops)

Full Kubernetes / GitOps manifests live in mcp-platform-gitops, not here. This repo only ships the app and image. The gitops repo should deploy it as a ClusterIP Service with these values:

Setting Value
Namespace mcp-gateway
Service name mock-mcp-server
Service type ClusterIP
Container port 8080
Internal DNS http://mock-mcp-server.mcp-gateway.svc.cluster.local:8080
Internal MCP URL http://mock-mcp-server.mcp-gateway.svc.cluster.local:8080/mcp
External MCP URL https://gateway.mcp.aidev.samsungds.net/mock/mcp (gateway)

Required Secret (mcp-internal-signing, key jwt-secret) holds the shared HS256 secret, which must match the gateway's signing secret and contain at least 32 bytes.

Container env:

env:
  - name: MCP_IDENTITY_JWT_SECRET
    valueFrom:
      secretKeyRef:
        name: mcp-internal-signing
        key: jwt-secret
  - name: MCP_IDENTITY_ISSUER
    value: mcp-auth-gateway
  - name: MCP_IDENTITY_AUDIENCE
    value: mock-mcp-server

Probes: GET /healthz (liveness), GET /readyz (readiness), both on 8080.

Do NOT create a public Ingress

This service must never be exposed with its own public Ingress / LoadBalancer.

  • It performs no external authentication (no Keycloak, no OAuth). It only trusts the gateway-signed X-MCP-Identity. A direct public route would let anyone reach /mcp while bypassing the gateway's real authentication.
  • All external access must go through the gateway, which authenticates the user, mints X-MCP-Identity, and routes /mock/mcp to this ClusterIP Service.

The only external entry point is:

https://gateway.mcp.aidev.samsungds.net/mock/mcp

Project layout

src/mock_mcp_server/
  __init__.py
  main.py          # ASGI app factory + uvicorn entrypoint
  settings.py      # env-based configuration (fails fast)
  identity.py      # X-MCP-Identity JWT verification (HS256; RS256-ready)
  middleware.py    # pure-ASGI identity enforcement (401) + scope injection
  context.py       # McpUserContext + get_current_user()
  tools.py         # whoami / echo / mock_profile / mock_search
scripts/
  make-dev-identity-token.py
tests/

from github.com/wontaeJeong/mock-mcp-server

Install Mock Server in Claude Desktop, Claude Code & Cursor

Recommended · one command, every IDE
unyly install mock-mcp-server

Installs into Claude Desktop, Claude Code, Cursor & VS Code — handles npx, uvx and build-from-source repos for you.

First time? Get the CLI: curl -fsSL https://unyly.org/install | sh

Or configure manually

Run in your terminal:

claude mcp add mock-mcp-server -- uvx mock-mcp-server

FAQ

Is Mock Server MCP free?

Yes, Mock Server MCP is free — one-click install via Unyly at no cost.

Does Mock Server need an API key?

No, Mock Server runs without API keys or environment variables.

Is Mock Server hosted or self-hosted?

Self-hosted: the server runs locally on your machine via the install command above.

How do I install Mock Server in Claude Desktop, Claude Code or Cursor?

Open Mock Server on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.

Related MCPs

Compare Mock Server with

Not sure what to pick?

Find your stack in 60 seconds

Author?

Embed badge for your README

Browse similar

All development MCPs