Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ target/
assets/
resources/
releases/
.conformance-suite/
conformance-logs/
conformance-results/
116 changes: 116 additions & 0 deletions .github/workflows/mcp_conformance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: MCP Conformance

on:
workflow_dispatch:
pull_request:
branches: [main]
push:
branches: [main]

permissions:
contents: read

concurrency:
group: mcp-conformance-${{ github.ref }}
cancel-in-progress: true

env:
MCP_CONFORMANCE_VERSION: 0.2.0-alpha.11
MCP_CONFORMANCE_SOURCE_SHA: c321dd32035556e6769d3724a8ee97d87c3faaac # pragma: allowlist secret
MCP_CONFORMANCE_SPEC_VERSION: 2026-07-28
MCP_CONFORMANCE_SERVER_ID: 3f33286667d34b65a31c3bafd30e4c21
CF_CONTROLPLANE_IMAGE: ghcr.io/ibm/mcp-context-forge:latest
CF_DATAPLANE_IMAGE: contextforge-data-plane:conformance

jobs:
conformance:
name: MCP 2026-07-28 through nginx and dataplane
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- name: Check out data plane
uses: actions/checkout@v6.0.2

- name: Build data plane from the checked-out source
run: make conformance-image

- name: Check out the alpha.11 conformance fixture
uses: actions/checkout@v6.0.2
with:
repository: modelcontextprotocol/conformance
ref: ${{ env.MCP_CONFORMANCE_SOURCE_SHA }}
path: .conformance-suite
persist-credentials: false

- name: Install Node.js 22
uses: actions/setup-node@v6.5.0
with:
node-version: 22
cache: npm
cache-dependency-path: .conformance-suite/package-lock.json

- name: Install official conformance dependencies
working-directory: .conformance-suite
run: |
test "$(node -p "require('./package.json').version")" = "${MCP_CONFORMANCE_VERSION}"
npm ci --ignore-scripts

- name: Pull external stack images
env:
MCP_CONFORMANCE_TOKEN: pull-only
run: >-
docker compose -f tests/conformance/docker-compose.yml
pull redis control-plane nginx

- name: Start the fixture and control plane
env:
MCP_CONFORMANCE_TOKEN: bootstrap-only
run: tests/conformance/start-fixture-and-control-plane.sh

- name: Register the official fixture through the control plane
env:
MCP_CONFORMANCE_TOKEN: bootstrap-only
run: tests/conformance/register-fixture.sh

- name: Start dataplane and nginx
run: tests/conformance/start-dataplane-and-nginx.sh

- name: Run MCP 2026-07-28 requirements
id: runner
run: tests/conformance/run-conformance.sh

- name: Print conformance failures and warnings
if: always()
run: |
if [ ! -d conformance-results ]; then
echo "No conformance results were produced."
exit 0
fi
find conformance-results -type f -name checks.json -print0 |
while IFS= read -r -d '' checks_file; do
jq --arg file "${checks_file}" \
'[.[] | select(.status == "FAILURE" or .status == "WARNING")] |
if length > 0 then {file: $file, findings: .} else empty end' \
"${checks_file}"
done

- name: Print live stack logs
if: always()
env:
MCP_CONFORMANCE_TOKEN: diagnostics-only
run: >-
docker compose -f tests/conformance/docker-compose.yml
logs --no-color || true

- name: Stop the live stack
Comment thread
lucarlig marked this conversation as resolved.
if: always()
env:
MCP_CONFORMANCE_TOKEN: cleanup-only
run: tests/conformance/stop-live-stack.sh

- name: Enforce conformance baseline
if: always()
env:
RUNNER_STATUS: ${{ steps.runner.outputs.status }}
run: test "${RUNNER_STATUS}" = "0"
6 changes: 3 additions & 3 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ jobs:
steps:
- uses: actions/checkout@v6.0.2
- name: Install mdBook
uses: taiki-e/install-action@v2.75.27
uses: taiki-e/install-action@v2.85.11
with:
tool: mdbook@0.5.3
- name: Build docs
run: mdbook build _context/wiki
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v4
uses: actions/upload-pages-artifact@v5.0.0
with:
path: _context/wiki/book

Expand All @@ -51,4 +51,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v5.0.0
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ target

# Generated by gateway local runs
contextforge-data-plane.log.*
# Generated by local MCP conformance runs
.conformance-suite/
conformance-logs/
conformance-results/
# Generated by mdBook (wiki)
_context/wiki/book/

Expand Down
16 changes: 8 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ repository = "https://github.com/contextforge-org/contextforge-data-plane"
# Keep dependencies here only when at least two workspace members inherit them.
contextforge-data-plane-cpex = { path = "./crates/contextforge-data-plane-cpex" }
contextforge-data-plane-apis = { path = "./crates/contextforge-data-plane-apis"}
rmcp = { version = "3.1.1", features = [
rmcp = { version = "3.1.2", features = [
"server",
"client",
"auth",
Expand Down
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
IMAGE_NAME := contextforge-data-plane:latest
CF_DATAPLANE_IMAGE ?= contextforge-data-plane:conformance
SERVICES ?= nginx control-plane redis postgres pgbouncer data-plane fast_time_server register_fast_time
ARGS ?=

# IBM detect-secrets hardened fork — pinned to the same commit used in mcp-context-forge.
DETECT_SECRETS_SPEC ?= git+https://github.com/ibm/detect-secrets.git@076672a9a01abdfc7ecee2e7d14f08cdccb73976
DETECT_SECRETS_EXCLUDE := '(?x)(Cargo\.lock$$|\.lock$$)|^\.secrets\.baseline$$'

.PHONY: help docker-prod compose-up compose-down docs-serve pre-commit secrets-scan-all configure-git
.PHONY: help docker-prod compose-up compose-down conformance-image conformance docs-serve pre-commit secrets-scan-all configure-git

help: ## Show available commands
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-22s\033[0m %s\n", $$1, $$2}'
Expand All @@ -24,6 +25,12 @@ compose-up: ## Launch stack: nginx, control plane, redis, postgres, pgbouncer, d
compose-down: ## Tear down the stack
docker compose -f docker/docker-compose.yml stop $(SERVICES) $(ARGS)

conformance-image: ## Build the current checkout for MCP conformance
docker build -t "$(CF_DATAPLANE_IMAGE)" -f docker/Dockerfile .

conformance: conformance-image ## Build and run official MCP 2026-07-28 conformance locally
CF_DATAPLANE_IMAGE="$(CF_DATAPLANE_IMAGE)" tests/conformance/run-local.sh

docs-serve: ## Serve the wiki book locally at http://127.0.0.1:3000
mdbook serve _context/wiki --hostname 127.0.0.1 --port 3000 --open

Expand Down
15 changes: 15 additions & 0 deletions _context/wiki/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ Protocol tests and fixtures should target MCP `2026-07-28`, use `server/discover

These run in `cargo nextest run` with no Docker dependencies.

## MCP Conformance CI

`.github/workflows/mcp_conformance.yml` runs the pinned official conformance
suite `0.2.0-alpha.11` with `--requirements 2026-07-28`. Its small live path is
official runner → nginx → published `latest` dataplane → official fixture,
with the published `latest` control plane registering and publishing the
fixture through Redis. The control plane uses ephemeral SQLite, so PostgreSQL
is unnecessary. The harness lives in `tests/conformance/`.

Because this conformance CLI cannot set a bearer header, nginx adds an
ephemeral control-plane token when one is absent; there is no auth proxy or
repository-owned JavaScript. A route probe prevents control-plane fallback.
Counts appear directly in the Actions log, and `expected-failures.yml` guards
the current baseline. The job does not retain a separate conformance artifact.

## Full-Stack Integration Harness

[`cf-integration`](https://github.com/contextforge-org/contextforge-dev-tools) wires the external ContextForge control plane to this dataplane the way production intends: the stock upstream Compose stack, plus exactly two intentional differences — nginx routes only `/servers/{virtual_host_id}/mcp` to the dataplane (as `/contextforge-rs/servers/{virtual_host_id}/mcp`), and the control plane runs with `DATAPLANE_PUBLISHER=true` so virtual server configs reach the dataplane through Redis.
Expand Down
113 changes: 113 additions & 0 deletions tests/conformance/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
name: contextforge-conformance

services:
redis:
image: redis:8.8.1-alpine3.23
command: [redis-server, --save, "", --appendonly, "no"]
networks: [contextforge]
healthcheck:
test: [CMD, redis-cli, ping]
interval: 2s
timeout: 2s
retries: 30

control-plane:
image: ${CF_CONTROLPLANE_IMAGE:-ghcr.io/ibm/mcp-context-forge:latest}
ports:
- "127.0.0.1:4444:4444"
networks: [contextforge]
extra_hosts:
- host.docker.internal:host-gateway
environment:
HOST: 0.0.0.0
PORT: "4444"
DATABASE_URL: sqlite:////tmp/contextforge-conformance.db
CACHE_TYPE: redis
REDIS_URL: redis://redis:6379/0
JWT_SECRET_KEY: contextforge-conformance-jwt-secret-at-least-32-bytes
JWT_ALGORITHM: RS256
JWT_PUBLIC_KEY_PATH: /keys/jwt.key.pub
JWT_PRIVATE_KEY_PATH: /keys/jwt.key
AUTH_ENCRYPTION_SECRET: contextforge-conformance-encryption-secret-at-least-32-bytes # pragma: allowlist secret
AUTH_REQUIRED: "true"
BASIC_AUTH_USER: admin
BASIC_AUTH_PASSWORD: conformance-admin-password # pragma: allowlist secret
PLATFORM_ADMIN_EMAIL: admin@example.com
PLATFORM_ADMIN_PASSWORD: conformance-admin-password # pragma: allowlist secret
MCPGATEWAY_SKIP_MIGRATIONS: "false"
MCPGATEWAY_UI_ENABLED: "false"
MCPGATEWAY_ADMIN_API_ENABLED: "true"
SECURE_COOKIES: "false"
SSRF_ALLOW_PRIVATE_NETWORKS: "true"
DATAPLANE_PUBLISHER: "true"
DATAPLANE_PUBLISHER_INTERVAL_SECONDS: "2"
PASSWORD_CHANGE_ENFORCEMENT_ENABLED: "false"
ADMIN_REQUIRE_PASSWORD_CHANGE_ON_BOOTSTRAP: "false"
REQUIRE_PASSWORD_CHANGE_FOR_DEFAULT_PASSWORD: "false"
GATEWAY_TOOL_NAME_SEPARATOR: _
GUNICORN_WORKERS: "1"
LOG_LEVEL: INFO
volumes:
- ../../assets:/keys:ro
depends_on:
redis:
condition: service_healthy
healthcheck:
test: [CMD, curl, --fail, http://127.0.0.1:4444/health]
interval: 5s
timeout: 5s
retries: 60
start_period: 10s

data-plane:
image: ${CF_DATAPLANE_IMAGE:-ghcr.io/contextforge-org/contextforge-data-plane:latest}
networks: [contextforge]
extra_hosts:
- host.docker.internal:host-gateway
environment:
CONTEXTFORGE_DATA_PLANE_ADDRESS: 0.0.0.0:4445
CONTEXTFORGE_DATA_PLANE_REDIS_HOSTNAME: redis
CONTEXTFORGE_DATA_PLANE_REDIS_PORT: "6379"
CONTEXTFORGE_DATA_PLANE_REDIS_CONNECTION_MODE: plain-text
CONTEXTFORGE_DATA_PLANE_UPSTREAM_CONNECTION_MODE: plain-text-or-tls
CONTEXTFORGE_DATA_PLANE_TOKEN_VERIFICATION_PUBLIC_KEY: /keys/jwt.key.pub
CONTEXTFORGE_DATA_PLANE_TOKEN_VERIFICATION_PRIVATE_KEY: /keys/jwt.key # pragma: allowlist secret
CONTEXTFORGE_DATA_PLANE_USER_CONFIG_CACHE_EXPIRY_SECONDS: "0"
CONTEXTFORGE_GATEWAY_RS_MCP_ALLOWED_HOSTS: >-
127.0.0.1:${MCP_CONFORMANCE_PORT:-8080},localhost:${MCP_CONFORMANCE_PORT:-8080}
CONTEXTFORGE_GATEWAY_RS_MCP_ALLOWED_ORIGINS: >-
http://127.0.0.1:${MCP_CONFORMANCE_PORT:-8080},http://localhost:${MCP_CONFORMANCE_PORT:-8080}
RUST_LOG: info
volumes:
- ../../assets:/keys:ro
depends_on:
redis:
condition: service_healthy

nginx:
image: nginx:1.30.4-alpine3.24
ports:
- "127.0.0.1:${MCP_CONFORMANCE_PORT:-8080}:80"
networks: [contextforge]
environment:
MCP_CONFORMANCE_TOKEN: ${MCP_CONFORMANCE_TOKEN:?}
NGINX_ENVSUBST_TEMPLATE_DIR: /templates
NGINX_ENVSUBST_TEMPLATE_SUFFIX: .template
NGINX_ENVSUBST_OUTPUT_DIR: /etc/nginx
volumes:
- ./nginx.conf.template:/templates/nginx.conf.template:ro
depends_on:
control-plane:
condition: service_healthy
# Full readiness, routing, and auth are verified by start-dataplane-and-nginx.sh.
data-plane:
condition: service_started
Comment thread
lucarlig marked this conversation as resolved.
healthcheck:
test: [CMD, curl, --fail, http://127.0.0.1/health]
interval: 2s
timeout: 2s
retries: 30

networks:
contextforge:
driver: bridge
Loading