Installation

IBM Verify MCP Server — Installation Guide

Overview

This guide walks you through deploying the IBM Verify MCP Server. Two deployment methods are supported:

MethodBest for
Option A — KubernetesProduction environments (single-pod; horizontal scaling is not yet supported)
Option B — Local DockerTesting and evaluation

🚀 Quick start: For a quick evaluation of the server, see Option B: Local Docker Deployment. Switch to Option A when you are ready for production.


Prerequisites

Before proceeding, ensure you have completed the Tenant Setup Prerequisites and have:

  • ✅ IBM Verify tenant configured with MCP Server applications
  • ✅ Client IDs and secrets for the Subject, STS, and Actor applications — see Tenant Setup
  • ✅ Users entitled to the "MCP Server Subject" application
  • ✅ Docker or Kubernetes environment ready
  • ✅ Nginx setup if deploying on a VM or Fyre environment

Step 1: Pull the Docker Image

The IBM Verify MCP Server is distributed as a Docker image from IBM Container Registry.

Image details:

  • Registry: icr.io/ibm-verify
  • Image: verify-mcp-server
  • Tag: latest
  • Full reference: icr.io/ibm-verify/verify-mcp-server:latest
docker pull icr.io/ibm-verify/verify-mcp-server:latest

# Verify the pull succeeded
docker images | grep verify-mcp-server

Expected output:

icr.io/ibm-verify/verify-mcp-server   latest   <image-id>   <size>   <time>

Step 2: Create deployment configuration file

config.yaml is the single source of truth for all server configuration. Every string value is processed before the server starts — values that begin with a recognized prefix are resolved from their source at startup; values with no prefix are used as-is.

Supported value prefixes

PrefixSyntaxWhat it doesRequires
(none)key: my-valueUsed as-is
!envvar!envvar VAR_NAMEReads from the named environment variable
!base64!base64 <encoded>Base64-decodes the payload
!file!file /path/to/fileReads entire contents of a fileFile accessible inside the container
!enc!enc <base64-ciphertext>RSA/PKCS#1-v1.5 decryptionENC_KEY env var
!obf!obf <base64-ciphertext>AES-256-CBC decryptionsecrets.obf_key in config.yaml
!secret!secret <secret-name>/<field>Reads a field from a Kubernetes SecretK8s cluster + RBAC
!configmap!configmap <configmap-name>/<field>Reads a field from a Kubernetes ConfigMapK8s cluster + RBAC

!base64 is encoding, not encryption. Do not use it to protect secrets in production — use !enc or !obf instead.

You can mix prefixes freely within a single file.

Using !enc or !obf for secrets: These prefixes encrypt credentials at rest inside config.yaml and are recommended for production. !enc requires an RSA key pair; !obf requires a shared passphrase. See Encryption & Obfuscation for step-by-step instructions on generating encrypted values and supplying the keys.

config.yaml template

The following template uses !secret and !configmap — the recommended model for Kubernetes. Templates for other deployment methods (Docker, .env file) are provided in the relevant deployment sections.

# IBM Verify OAuth MCP Server — Configuration File
#
# Every string value supports a normalization prefix that controls how it is
# resolved at startup. Mix and match freely within this file.
#
# PREFIX REFERENCE:
#   plain value               — used as-is (safe for non-sensitive config)
#   !envvar VAR_NAME          — reads from environment variable VAR_NAME
#   !base64 <encoded>         — base64-decodes the payload inline
#   !file /path/to/file       — reads the entire contents of a file
#   !enc <base64-ciphertext>  — RSA decryption; requires ENC_KEY env var
#                               ⚠ Only needed when using !enc-prefixed values.
#                               See: Encryption & Obfuscation guide.
#   !obf <base64-ciphertext>  — AES-256-CBC decryption; requires secrets.obf_key
#                               ⚠ Only needed when using !obf-prefixed values.
#                               See: Encryption & Obfuscation guide.
#   !secret <name>/<field>    — reads from a Kubernetes Secret  (K8s only)
#   !configmap <name>/<field> — reads from a Kubernetes ConfigMap  (K8s only)
#
# Default location: config.yaml in the working directory.

# ---------------------------------------------------------------------------
# Secrets
# Only required when using !obf-prefixed values. obf_key is the passphrase
# used to decrypt them. Remove this entire section if you are not using !obf.
# ---------------------------------------------------------------------------
secrets:
  obf_key: !secret verify-mcp-credentials/obf_key

# ---------------------------------------------------------------------------
# Server — network binding.
# ---------------------------------------------------------------------------
server:
  host: 0.0.0.0   # Listen on all interfaces
  port: 8000

# ---------------------------------------------------------------------------
# IBM Verify — OAuth credentials and endpoint configuration.
#
# Required fields:
#   tenant_url, client_id, client_secret, base_url,
#   sts_client_id, sts_client_secret, actor_client_id, actor_client_secret
#
# Optional fields are auto-constructed from tenant_url if omitted.
# ---------------------------------------------------------------------------
ibm_verify:
  # IBM Verify tenant base URL
  # Example: https://my-tenant.verify.ibm.com
  tenant_url: !configmap verify-mcp-config/IBM_VERIFY_TENANT_URL
  # tenant_url: https://my-tenant.verify.ibm.com   # plain text alternative

  # Subject application OAuth credentials
  client_id:     !secret verify-mcp-credentials/client-id
  # client_id: my-subject-client-id                # plain text example
  client_secret: !secret verify-mcp-credentials/client-secret
  # client_secret: my-subject-client-secret        # plain text; use !enc or !obf in production

  # Public URL of this MCP server — used as the OAuth redirect base.
  # Use http://localhost:8000 for local testing.
  # Use your public HTTPS hostname for production deployments.
  base_url: https://mcp-server.example.com

  # STS client for token exchange
  sts_client_id:     !secret verify-mcp-credentials/sts-client-id
  # sts_client_id: my-sts-client-id                # plain text example
  sts_client_secret: !secret verify-mcp-credentials/sts-client-secret
  # sts_client_secret: my-sts-client-secret        # plain text; use !enc or !obf in production

  # Actor client for enhanced audit logging
  actor_client_id:     !secret verify-mcp-credentials/actor-client-id
  # actor_client_id: my-actor-client-id            # plain text example
  actor_client_secret: !secret verify-mcp-credentials/actor-client-secret
  # actor_client_secret: my-actor-client-secret    # plain text; use !enc or !obf in production

  # Recommended for production: restrict allowed OAuth redirect URIs.
  # If unset, any redirect URI is accepted — open-redirect risk.
  # allowed_redirect_uris: https://client.mycompany.com/callback

  # Optional — auto-constructed from tenant_url if omitted
  # oidc_config_url:    https://...
  # token_exchange_url: https://...
  # introspection_url:  https://...

  scopes: openid profile email

# ---------------------------------------------------------------------------
# Transport
# The only accepted value is streamable-http.
# ---------------------------------------------------------------------------
transport:
  type: streamable-http
  # authentication_method: oidc_proxy   # default; change to direct_token if needed

# ---------------------------------------------------------------------------
# Cache — token cache (streamable-http only).
# ---------------------------------------------------------------------------
cache:
  ttl: 900        # seconds
  maxsize: 1000   # max cached tokens

# ---------------------------------------------------------------------------
# Logging
# ---------------------------------------------------------------------------
logging:
  level: INFO   # DEBUG | INFO | WARNING | ERROR | CRITICAL

  # Log file inside the container.
  file: /app/logs/mcp-server.log
  max_bytes: 10485760   # 10 MB per file
  backup_count: 5

config.yaml Configuration Reference

All the supported configuration parameters are listed as follows. Required parameters must be present or the server doesn't start.

secrets section

ParameterTypeRequiredDescription
secrets.obf_keystringOnly if using !obf valuesPassphrase used to decrypt !obf-prefixed values (AES-256-CBC/PBKDF2-SHA512). Never store in plain text — supply through !envvar or !secret.
secrets:
  obf_key: !envvar OBF_KEY

server section

ParameterTypeRequiredDefaultDescription
server.hoststring✅ Yes0.0.0.0Network interface the server listens on.
server.portinteger✅ Yes8000Port the server listens on.

ibm_verify section

ParameterTypeRequiredDescription
ibm_verify.tenant_urlstring✅ YesIBM Verify tenant base URL. Example: https://mytenant.verify.ibm.com
ibm_verify.client_idstring✅ YesOAuth client ID for the Subject application.
ibm_verify.client_secretstring✅ YesOAuth client secret. Use !enc or !obf in production.
ibm_verify.base_urlstring✅ YesPublic URL of this MCP server, used as the OAuth redirect base (streamable-http only).
ibm_verify.sts_client_idstring✅ YesClient ID for the STS application (token exchange).
ibm_verify.sts_client_secretstring✅ YesClient secret for the STS application. Use !enc or !obf in production.
ibm_verify.actor_client_idstring✅ YesClient ID for the Actor application (audit logging).
ibm_verify.actor_client_secretstring✅ YesClient secret for the Actor application. Use !enc or !obf in production.
ibm_verify.oidc_config_urlstringNoOIDC discovery document URL. Auto-constructed from tenant_url if omitted.
ibm_verify.token_exchange_urlstringNoToken exchange endpoint URL. Auto-constructed from tenant_url if omitted.
ibm_verify.introspection_urlstringNoToken introspection endpoint URL. Auto-constructed from tenant_url if omitted.
ibm_verify.scopesstringNoSpace-separated OAuth scopes. Default: openid profile email
ibm_verify.allowed_redirect_urisstringNoComma-separated list of allowed OAuth redirect URIs. Always set this in production to prevent open-redirect attacks.

transport section

ParameterTypeRequiredDefaultDescription
transport.typestring✅ Yesstreamable-httpTransport protocol. Only accepted value: streamable-http.
transport.authentication_methodstringNooidc_proxyAuthentication method for streamable-http. Accepted values: oidc_proxy, direct_token.

cache section (streamable-http only)

ParameterTypeRequiredDefaultDescription
cache.ttlintegerNo900Token cache time-to-live in seconds.
cache.maxsizeintegerNo1000Maximum number of tokens held in cache.

logging section

ParameterTypeRequiredDefaultDescription
logging.levelstring✅ YesINFOLog verbosity: DEBUG, INFO, WARNING, ERROR, CRITICAL.
logging.filestringNo(stderr)Path to log file inside the container.
logging.max_bytesintegerNo10485760Maximum log file size before rotation (bytes). Default is 10 MB.
logging.backup_countintegerNo5Number of rotated log file backups to retain.

Step 3: Choose Your Deployment Method

Select the path that matches your environment:


Option A: Kubernetes Deployment

This is the recommended deployment model for production. Credentials are resolved directly from Kubernetes Secrets at pod startup — they are never written to environment variables or the filesystem.

How it works

  1. Non-sensitive configuration (tenant URL, server port, etc.) is stored in a ConfigMap, created from config.yaml.
  2. All secrets (client credentials, obfuscation key) are stored in a Kubernetes Secret and referenced in config.yaml by using the !secret prefix.
  3. If you use !enc-encrypted values, the RSA private key is stored in a separate Kubernetes Secret and referenced through ENC_KEY.

A.1 Prerequisites

  • Kubernetes (v1.19+) or OpenShift cluster
  • kubectl / oc CLI configured to access your cluster
  • Ingress controller (nginx, Traefik, etc.) or an OpenShift Route for external access
  • IBM Verify tenant credentials from Tenant Setup

A.2 Create a Namespace

NAMESPACE=verify-mcp-server

kubectl create namespace "${NAMESPACE}"

A.3 Create the Credentials Secret

Store all sensitive values in a Kubernetes Secret. The config.yaml template in Step 2 references these by using !secret verify-mcp-credentials/<key>.

kubectl create secret generic verify-mcp-credentials \
  --namespace="${NAMESPACE}" \
  --from-literal=client-id='<subject-app-client-id>' \
  --from-literal=client-secret='<subject-app-client-secret>' \
  --from-literal=sts-client-id='<sts-app-client-id>' \
  --from-literal=sts-client-secret='<sts-app-client-secret>' \
  --from-literal=actor-client-id='<actor-app-client-id>' \
  --from-literal=actor-client-secret='<actor-app-client-secret>'
  # Add --from-literal=obf_key='<passphrase>' only if using !obf values

Using encryption: You can store !enc <ciphertext> values here instead of plain text. See Encryption & Obfuscation for instructions on generating ciphertexts.

Update config.yaml to set all client IDs and secrets to reference the secrets created above:

  ibm_verify:
    client_id:     !secret verify-mcp-credentials/client-id
    client_secret: !secret verify-mcp-credentials/client-secret
    sts_client_id:     !secret verify-mcp-credentials/sts-client-id
    sts_client_secret: !secret verify-mcp-credentials/sts-client-secret
    actor_client_id:     !secret verify-mcp-credentials/actor-client-id
    actor_client_secret: !secret verify-mcp-credentials/actor-client-secret
  # only if using !obf values is used
  secrets:
    obf_key: !secret verify-mcp-credentials/obf_key

A.4 Create the RSA Private Key Secret (only if using !enc)

Step A.4 is required only when any value in your config.yaml uses the !enc prefix. The RSA private key is used to decrypt the !enc-prefixed values at startup.

kubectl create secret generic verify-mcp-enc-key \
  --namespace="${NAMESPACE}" \
  --from-file=private.pem=./private.pem

The deployment manifest (Step A.6) sets ENC_KEY to reference this secret:

- name: ENC_KEY
  value: "!secret verify-mcp-enc-key/private.pem"

Keep private.pem out of version control. Add it to .gitignore and use a secrets manager for production deployments. The !secret approach means the PEM content is fetched directly from the cluster — it never touches an env var or mounted file.


A.5 Create the ConfigMap from config.yaml

If your config.yaml uses !configmap verify-mcp-config/<key> references (as in the template above), create that ConfigMap:

kubectl create configmap verify-mcp-config \
  --namespace="${NAMESPACE}" \
  --from-literal=IBM_VERIFY_TENANT_URL='https://mytenant.verify.ibm.com'

Update config.yaml to set tenant_url to reference the ConfigMap previously created:

  ibm_verify:
    tenant_url: !configmap verify-mcp-config/IBM_VERIFY_TENANT_URL

Validate config.yaml, then load it into Kubernetes as a ConfigMap so it can be mounted into the container:

kubectl create configmap verify-mcp-files \
  --namespace="${NAMESPACE}" \
  --from-file=config.yaml=./config.yaml

A.6 Apply the Deployment Manifest

Save the following as verify-mcp-server.yaml and apply it. Update the namespace, image tag, and base_url in config.yaml before applying.

# ============================================================================
# IBM Verify MCP Server — Kubernetes manifest
# Apply with: kubectl apply -f verify-mcp-server.yaml
# ============================================================================

# ----------------------------------------------------------------------------
# 1. ServiceAccount
#    A dedicated ServiceAccount instead of the default namespace SA.
# ----------------------------------------------------------------------------
apiVersion: v1
kind: ServiceAccount
metadata:
  name: verify-mcp-server-sa
  namespace: verify-mcp-server

---
# ----------------------------------------------------------------------------
# 2. Role
#    Grants the pod permission to read Secrets and ConfigMaps.
#    Required for !secret and !configmap prefix resolution.
# ----------------------------------------------------------------------------
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: verify-mcp-server-role
  namespace: verify-mcp-server
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list"]
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list"]

---
# ----------------------------------------------------------------------------
# 3. RoleBinding
# ----------------------------------------------------------------------------
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: verify-mcp-server-rolebinding
  namespace: verify-mcp-server
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: verify-mcp-server-role
subjects:
  - kind: ServiceAccount
    name: verify-mcp-server-sa
    namespace: verify-mcp-server

---
# ----------------------------------------------------------------------------
# 4. Deployment
# ----------------------------------------------------------------------------
apiVersion: apps/v1
kind: Deployment
metadata:
  name: verify-mcp-server
  namespace: verify-mcp-server
  labels:
    app: verify-mcp-server
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: verify-mcp-server
  template:
    metadata:
      labels:
        app: verify-mcp-server
        version: v1
      annotations:
        instana.io/instrumentation: "false"
    spec:
      # securityContext can be updated as per security requirement
      securityContext: {}
      serviceAccountName: verify-mcp-server-sa

      containers:
      - name: verify-mcp-server
        image: icr.io/ibm-verify/verify-mcp-server:latest
        imagePullPolicy: Always

        # DO NOT override command — doing so bypasses all prefix resolution
        # and the server will fail to start.

        ports:
        - name: http
          containerPort: 8000
          protocol: TCP

        env:
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace

        # ENC_KEY — required ONLY when config.yaml contains !enc-prefixed values.
        # Points to the RSA private key used to decrypt those values.
        # Using !secret here means the PEM content is fetched directly from the cluster secrets store — it never touches a mounted file or env var.
        # Remove this entry entirely if you are not using !enc.
        - name: ENC_KEY
          value: "!secret verify-mcp-enc-key/private.pem"

        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "1Gi"
            cpu: "1000m"

        # Liveness probe — 30s delay allows config resolution to complete before the probe fires.
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 30
          periodSeconds: 15
          timeoutSeconds: 5
          failureThreshold: 3

        readinessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 10
          periodSeconds: 10
          timeoutSeconds: 3
          failureThreshold: 3

        volumeMounts:
        - name: logs
          mountPath: /app/logs
        # config.yaml is mounted read-only from the verify-mcp-files ConfigMap.
        - name: mcp-files
          mountPath: /app/config/config.yaml
          subPath: config.yaml
          readOnly: true

      volumes:
      - name: logs
        emptyDir: {}
      - name: mcp-files
        configMap:
          name: verify-mcp-files

      restartPolicy: Always

---
# ----------------------------------------------------------------------------
# 5. Service
# ----------------------------------------------------------------------------
apiVersion: v1
kind: Service
metadata:
  name: verify-mcp-server
  namespace: verify-mcp-server
  labels:
    app: verify-mcp-server
spec:
  type: ClusterIP
  ports:
  - port: 8000
    targetPort: 8000
    protocol: TCP
    name: http
  selector:
    app: verify-mcp-server
kubectl apply -f verify-mcp-server.yaml

Expected output:

serviceaccount/verify-mcp-server-sa is created
role.rbac.authorization.k8s.io/verify-mcp-server-role is created
rolebinding.rbac.authorization.k8s.io/verify-mcp-server-rolebinding is created
deployment.apps/verify-mcp-server is created
service/verify-mcp-server is created

A.7 Expose through Ingress or Route

Add the appropriate resource to expose the service externally.

For OpenShift (Route):

apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: verify-mcp-server
  namespace: verify-mcp-server
  labels:
    app: verify-mcp-server
spec:
  host: <external-hostname, e.g. verify-mcp-server.example.com>
  to:
    kind: Service
    name: verify-mcp-server
    weight: 100
  port:
    targetPort: http
  tls:
    termination: edge
    insecureEdgeTerminationPolicy: Redirect
  wildcardPolicy: None

For standard Kubernetes (Ingress):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: verify-mcp-server
  namespace: verify-mcp-server
  labels:
    app: verify-mcp-server
  annotations:
    # Adjust for your ingress controller, e.g.:
    # nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host: <external-hostname, e.g. verify-mcp-server.example.com>
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: verify-mcp-server
            port:
              number: 8000

A.8 Verify the Deployment

# Check all resources in the namespace
kubectl get all -n "${NAMESPACE}"

# Check configmaps and secrets
kubectl get configmap,secret -n "${NAMESPACE}"

Expected output:

NAME                                     READY   STATUS    RESTARTS   AGE
pod/verify-mcp-server-xxxxxxxxxx-xxxxx   1/1     Running   0          30s

NAME                        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/verify-mcp-server   ClusterIP   10.96.xxx.xxx   <none>        8000/TCP   30s

NAME                                READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/verify-mcp-server   1/1     1            1           30s

NAME                              DATA   AGE
configmap/verify-mcp-config       1      30s
configmap/verify-mcp-files        1      30s

NAME                             TYPE     DATA   AGE
secret/verify-mcp-credentials   Opaque   6      30s
secret/verify-mcp-enc-key       Opaque   1      30s

A.9 Test the Deployment

# Port-forward to test locally
kubectl port-forward -n "${NAMESPACE}" svc/verify-mcp-server 8000:8000

# In a separate terminal
curl http://localhost:8000/health

Expected response:

{
  "status": "healthy",
  "service": "IBM Verify MCP Server"
}

If the health check fails, inspect logs: kubectl logs -n "${NAMESPACE}" -l app=verify-mcp-server --tail=50. Common causes are missing or misconfigured secrets, or an unreachable IBM Verify tenant. See the Troubleshooting Guide.


A.10 Kubernetes Management Commands

View logs:

# Follow logs
kubectl logs -f -n "${NAMESPACE}" -l app=verify-mcp-server

# View last 100 lines
kubectl logs -n "${NAMESPACE}" -l app=verify-mcp-server --tail=100

Update configuration:

# Recreate the ConfigMap from an updated config.yaml:
kubectl create configmap verify-mcp-files \
  --namespace="${NAMESPACE}" \
  --from-file=config.yaml=./config.yaml \
  --dry-run=client -o yaml | kubectl apply -f -

# Restart the deployment to pick up changes:
kubectl rollout restart deployment verify-mcp-server -n "${NAMESPACE}"

Update image:

# Update the image tag in verify-mcp-server.yaml, then re-apply:
kubectl apply -f verify-mcp-server.yaml

# Or update in-place:
kubectl set image deployment/verify-mcp-server \
  verify-mcp-server=icr.io/ibm-verify/verify-mcp-server:latest \
  -n "${NAMESPACE}"

kubectl rollout status deployment verify-mcp-server -n "${NAMESPACE}"

Scale:

# Only single-pod deployment is currently supported.
# Do not scale above 1 — session/cache state is in-process and not shared
# across pods. HA support is planned for a future release.
kubectl scale deployment verify-mcp-server -n "${NAMESPACE}" --replicas=1

Delete deployment:

kubectl delete namespace "${NAMESPACE}"

Option B: Local Docker Deployment

Run the MCP Server locally for testing or evaluation.

Prerequisites

  • Docker installed and running
  • Access to IBM Container Registry (icr.io)
  • config.yaml created in Step 2

B.1 Prepare the Working Directory

mkdir -p ./config ./logs
cp /path/to/config.yaml ./config/config.yaml
./
├── config/
│   └── config.yaml     ← required
└── logs/               ← mount to persist logs

If you are using !enc-prefixed values, also create a secrets/ folder and place your private.pem inside it.

A minimal config.yaml for Docker to reference credentials:

# config.yaml for Docker / local deployment

# Either we can edit this file directly or make Uses !envvar so credentials are loaded from the .env file or -e flags at runtime.

# Credentials can be set as plain text, or resolved at runtime using !envvar, !enc, or !obf prefixes.

secrets:
  obf_key: !envvar OBF_KEY          # Needed only when using !obf-prefixed values; supply the passphrase through an environment variable

server:
  host: 0.0.0.0
  port: 8000

ibm_verify:
  tenant_url:    https://<my-tenant.verify.ibm.com>
  client_id:    <your-subject-client-id>      # For production: !enc <base64-ciphertext>
  client_secret: <your-subject-client-secret>      # For production: !enc <base64-ciphertext>
  base_url:      https://<mcp-server.example.com>
  sts_client_id:     <your-sts-client-id>      # For production: !enc <base64-ciphertext>
  sts_client_secret: <your-sts-client-secret>      # For production: !enc <base64-ciphertext>
  actor_client_id:     <your-actor-client-id>      # For production: !enc <base64-ciphertext>
  actor_client_secret: <your-actor-client-secret>      # For production: !enc <base64-ciphertext>
  scopes: openid profile email

transport:
  type: streamable-http

logging:
  level: INFO
  file: /app/logs/mcp-server.log

B.2 Run the Container

Apple Silicon / non-amd64 hosts: Add --platform linux/amd64 immediately after docker run.

Run the following command, including the optional flags that match how your config.yaml is configured:

docker run -d \
  --name verify-mcp-server \
  -p 8000:8000 \
  -v "$(pwd)/config:/app/config:ro" \
  -v "$(pwd)/logs:/app/logs" \
  # [Optional: Only if using !enc (RSA encryption)] Mount private key and set ENC_KEY
  # -v "$(pwd)/secrets:/app/secrets:ro" \
  # -e ENC_KEY="!file /app/secrets/private.pem" \
  # [Optional: Only if using !envvar in config.yaml] Load environment variables
  # --env-file .env \
  --restart unless-stopped \
  icr.io/ibm-verify/verify-mcp-server:latest

Additional parameters by configuration type

  • When using !enc (RSA encryption):
    Mount your private key directory and point ENC_KEY at the file path inside the container:

    -v "$(pwd)/secrets:/app/secrets:ro" \
    -e ENC_KEY="!file /app/secrets/private.pem"

    (Note: The path in ENC_KEY must match the mount path inside the container).

  • When using !obf (Obfuscation):
    Provide the passphrase through secrets.obf_key in config.yaml. Never store the passphrase as plain text in config.yaml — supply it at runtime through an environment variable or a mounted file:

    secrets:
      # obf_key: MyMasterPassword        # ⚠️ INSECURE — plain text in config file; do not use in production
      obf_key: !envvar OBF_KEY           # recommended: read from environment variable
      # obf_key: !file /app/secrets/obf  # alternative: read from a mounted file
  • When using !envvar:
    Pass variables using --env-file .env or individual -e KEY="value" flags.


B.3 Confirm the Container is Running

docker ps | grep verify-mcp-server
docker logs verify-mcp-server --tail=30

B.4 Container Management

docker logs -f verify-mcp-server          # follow live logs
docker logs verify-mcp-server --tail=50   # view last 50 lines
docker stop verify-mcp-server
docker start verify-mcp-server
docker restart verify-mcp-server
docker rm -f verify-mcp-server

Validate the Installation

Regardless of deployment method, confirm the server is running correctly before proceeding.

Health Check

# Docker
curl http://localhost:8000/health

# Kubernetes (through port-forward)
kubectl port-forward -n "${NAMESPACE}" svc/verify-mcp-server 8000:8000
curl http://localhost:8000/health

# Kubernetes (through ingress hostname)
curl https://mcp.mycompany.com/health

Expected response:

{
  "status": "healthy",
  "service": "IBM Verify MCP Server"
}

Verify Server Logs

# Docker
docker logs verify-mcp-server --tail=50

# Kubernetes
kubectl logs -n "${NAMESPACE}" -l app=verify-mcp-server --tail=50

Look for:

  • ✅ Server started successfully
  • ✅ No error messages
  • ✅ Listening on port 8000

Post-Installation: Nginx Reverse Proxy (VM / Fyre)

When deploying on a VM or Fyre environment, the server runs on plain HTTP (port 8000). Because MCP clients require HTTPS, place an nginx reverse proxy in front of it.

  • nginx terminates HTTPS on port 443 (or a custom port such as 2443)
  • nginx proxies requests to http://localhost:8000

Self-signed certificates: In Fyre or lab environments you can generate a self-signed TLS certificate. MCP clients must be configured to trust it. See the Configuring MCP Clients guide for self-signed certificate setup instructions.

For nginx setup instructions, refer to the nginx reverse proxy guide or the DigitalOcean nginx reverse proxy tutorial.


Resource and Network Requirements

Compute (per pod / container):

CPUMemory
Minimum500m (0.5 cores)512 Mi
Recommended1000m (1 core)1 Gi

Network:

DirectionPortProtocolPurpose
Inbound8000HTTPMCP Server API
Outbound443HTTPSIBM Verify tenant communication

Troubleshooting

For a full list of issues and solutions, see the Troubleshooting Guide.

Quick debug commands:

# Docker — check logs
docker logs verify-mcp-server

Next Steps

Once the server is running:

  1. 👉 Configure MCP Clients — Connect Claude Desktop, IBM Bob, or other MCP-compatible clients
  2. ✅ Test the authentication flow end-to-end with an entitled user
  3. ✅ Review Encryption & Obfuscation to harden credentials for production
  4. ✅ Set allowed_redirect_uris in config.yaml to lock down OAuth redirect targets
  5. ✅ Set up monitoring, log retention, and alerting

Additional Resources


Support

For issues or questions:



Did this page help you?