diff --git a/scripts/testing/official_mcp_conformance.py b/scripts/testing/official_mcp_conformance.py new file mode 100644 index 000000000..a1adcb47a --- /dev/null +++ b/scripts/testing/official_mcp_conformance.py @@ -0,0 +1,388 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import socket +import subprocess +import sys +import tempfile +import time +from collections import Counter +from pathlib import Path +from typing import Any + +REPO_ROOT = Path(__file__).resolve().parents[2] +FIXTURE_SERVER = REPO_ROOT / "tests/testing/official_mcp_fixture_server.py" +AUTH_CLIENT = REPO_ROOT / "tests/testing/official_mcp_auth_client.mjs" +DEFAULT_RECEIPT = ( + REPO_ROOT / "tests/fixtures/mcp_conformance/official-2026-07-28-receipt.json" +) +CONFORMANCE_COMMIT = "a983ba93c91e0bb31d0b6849eeb52f0ad1083107" +CONFORMANCE_PACKAGE = ( + f"git+https://github.com/modelcontextprotocol/conformance.git#{CONFORMANCE_COMMIT}" +) +SERVER_SCENARIOS = ( + {"scenario": "tools-list", "spec_version": "2026-07-28", "required": True}, + { + "scenario": "tools-call-simple-text", + "spec_version": "2026-07-28", + "required": True, + }, + {"scenario": "tools-call-error", "spec_version": "2026-07-28", "required": True}, + {"scenario": "server-initialize", "spec_version": "2025-11-25", "required": True}, +) +CLIENT_SCENARIOS = ( + {"scenario": "auth/metadata-var2", "spec_version": "2026-07-28", "required": True}, + { + "scenario": "auth/token-endpoint-auth-basic", + "spec_version": "2026-07-28", + "required": True, + }, + { + "scenario": "auth/token-endpoint-auth-post", + "spec_version": "2026-07-28", + "required": True, + }, + { + "scenario": "auth/token-endpoint-auth-none", + "spec_version": "2026-07-28", + "required": True, + }, +) +EXCLUSIONS = { + "server": [ + { + "reason": "surface-not-implemented", + "scenarios": [ + "server-stateless", + "completion-complete", + "tools-call-image", + "tools-call-audio", + "tools-call-embedded-resource", + "tools-call-mixed-content", + "tools-call-with-progress", + "server-sse-multiple-streams", + "resources-list", + "resources-read-text", + "resources-read-binary", + "resources-templates-read", + "sep-2164-resource-not-found", + "prompts-list", + "prompts-get-simple", + "prompts-get-with-args", + "prompts-get-embedded-resource", + "prompts-get-with-image", + "dns-rebinding-protection", + "caching", + "input-required-result-basic-elicitation", + "input-required-result-basic-sampling", + "input-required-result-basic-list-roots", + "input-required-result-request-state", + "input-required-result-multiple-input-requests", + "input-required-result-multi-round", + "input-required-result-missing-input-response", + "input-required-result-non-tool-request", + "input-required-result-result-type", + "input-required-result-unsupported-methods", + "input-required-result-tampered-state", + "input-required-result-capability-check", + "input-required-result-ignore-extra-params", + "input-required-result-validate-input", + ], + }, + { + "reason": "extension-not-implemented", + "scenarios": [ + "tasks-lifecycle", + "tasks-capability-negotiation", + "tasks-wire-fields", + "tasks-request-state-removal", + "tasks-mrtr-input", + "tasks-request-headers", + "tasks-dispatch-and-envelope", + "tasks-status-notifications", + "tasks-required-task-error", + "tasks-mrtr-composition", + ], + }, + ], + "client": [ + { + "reason": "surface-not-certified-in-this-baseline", + "scenarios": [ + "tools_call", + "request-metadata", + "auth/metadata-default", + "auth/metadata-var1", + "auth/metadata-var3", + "auth/basic-cimd", + "auth/scope-from-www-authenticate", + "auth/scope-from-scopes-supported", + "auth/scope-omitted-when-undefined", + "auth/scope-step-up", + "auth/scope-retry-limit", + "auth/pre-registration", + "auth/resource-mismatch", + "auth/offline-access-scope", + "auth/offline-access-not-supported", + "auth/authorization-server-migration", + "auth/iss-supported", + "auth/iss-not-advertised", + "auth/iss-supported-missing", + "auth/iss-wrong-issuer", + "auth/iss-unexpected", + "auth/iss-normalized", + "auth/metadata-issuer-mismatch", + "sep-2322-client-request-state", + "http-standard-headers", + "http-custom-headers", + "http-invalid-tool-headers", + "json-schema-ref-no-deref", + ], + }, + { + "reason": "extension-not-implemented", + "scenarios": [ + "auth/client-credentials-jwt", + "auth/client-credentials-basic", + "auth/enterprise-managed-authorization", + "auth/dpop", + "auth/dpop-nonce", + "auth/wif-jwt-bearer", + "json-schema-2020-12-preservation", + ], + }, + ], +} + + +def summarize_checks( + checks: list[dict[str, Any]], + *, + required: bool, +) -> dict[str, Any]: + counts = Counter(str(check.get("status", "UNKNOWN")) for check in checks) + blocking = [ + f"{check.get('id', '')}:{check.get('status', 'UNKNOWN')}" + for check in checks + if required and str(check.get("status")) in {"FAILURE", "WARNING"} + ] + return { + "ok": not blocking, + "counts": dict(counts), + "blocking": blocking, + } + + +def _run( + args: list[str], + *, + cwd: Path = REPO_ROOT, +) -> subprocess.CompletedProcess[str]: + return subprocess.run( + args, + cwd=cwd, + text=True, + capture_output=True, + check=False, + ) + + +def _free_port() -> int: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.bind(("127.0.0.1", 0)) + return int(sock.getsockname()[1]) + + +def _wait_for_port(port: int, timeout: float = 10.0) -> None: + deadline = time.time() + timeout + while time.time() < deadline: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + if sock.connect_ex(("127.0.0.1", port)) == 0: + return + time.sleep(0.05) + raise RuntimeError(f"fixture server did not start on port {port}") + + +def _load_checks(output_dir: Path) -> list[dict[str, Any]]: + candidates = sorted(output_dir.glob("**/checks.json")) + if not candidates: + raise FileNotFoundError(f"no checks.json found under {output_dir}") + return json.loads(candidates[-1].read_text()) + + +def _npx_prefix() -> list[str]: + return [ + "npx", + "--yes", + f"--package={CONFORMANCE_PACKAGE}", + "conformance", + ] + + +def _run_server_scenario(config: dict[str, Any]) -> dict[str, Any]: + port = _free_port() + with tempfile.TemporaryDirectory(prefix="mcp-conformance-server-") as tmpdir: + output_dir = Path(tmpdir) + server = subprocess.Popen( + [sys.executable, str(FIXTURE_SERVER), "--port", str(port)], + cwd=REPO_ROOT, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + try: + _wait_for_port(port) + cmd = _npx_prefix() + [ + "server", + "--url", + f"http://127.0.0.1:{port}/mcp", + "--scenario", + str(config["scenario"]), + "--spec-version", + str(config["spec_version"]), + "-o", + str(output_dir), + ] + result = _run(cmd) + checks = _load_checks(output_dir) + finally: + server.terminate() + server.wait(timeout=5) + + summary = summarize_checks(checks, required=bool(config["required"])) + return { + "leg": "server", + "scenario": config["scenario"], + "spec_version": config["spec_version"], + "required": config["required"], + "exit_code": result.returncode, + "command": cmd, + "stdout": result.stdout, + "stderr": result.stderr, + "checks": checks, + "summary": summary, + } + + +def _run_client_scenario(config: dict[str, Any]) -> dict[str, Any]: + with tempfile.TemporaryDirectory(prefix="mcp-conformance-client-") as tmpdir: + output_dir = Path(tmpdir) + cmd = _npx_prefix() + [ + "client", + "--command", + f"node {AUTH_CLIENT}", + "--scenario", + str(config["scenario"]), + "--spec-version", + str(config["spec_version"]), + "-o", + str(output_dir), + ] + result = _run(cmd) + checks = _load_checks(output_dir) + + summary = summarize_checks(checks, required=bool(config["required"])) + return { + "leg": "client", + "scenario": config["scenario"], + "spec_version": config["spec_version"], + "required": config["required"], + "exit_code": result.returncode, + "command": cmd, + "stdout": result.stdout, + "stderr": result.stderr, + "checks": checks, + "summary": summary, + } + + +def _trimmed(text: str, limit: int = 4000) -> str: + return text if len(text) <= limit else text[:limit] + "\n...[truncated]" + + +def build_receipt(run_records: list[dict[str, Any]]) -> dict[str, Any]: + implementation_commit = _run(["git", "rev-parse", "HEAD"]).stdout.strip() + node_version = _run(["node", "--version"]).stdout.strip() + npm_version = _run(["npm", "--version"]).stdout.strip() + package_json = json.loads((REPO_ROOT / "package.json").read_text()) + + overall_ok = all(record["summary"]["ok"] for record in run_records if record["required"]) + runs = [] + for record in run_records: + runs.append( + { + "leg": record["leg"], + "scenario": record["scenario"], + "spec_version": record["spec_version"], + "required": record["required"], + "exit_code": record["exit_code"], + "summary": record["summary"], + "warnings": [ + check["id"] + for check in record["checks"] + if check.get("status") == "WARNING" + ], + "failures": [ + check["id"] + for check in record["checks"] + if check.get("status") == "FAILURE" + ], + "checks": record["checks"], + "stdout": _trimmed(record["stdout"]), + "stderr": _trimmed(record["stderr"]), + } + ) + + return { + "schema_version": "eventrelay.mcp-conformance-receipt.v1", + "baseline_revision": "2026-07-28", + "generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + "overall_ok": overall_ok, + "conformance": { + "package": CONFORMANCE_PACKAGE, + "commit": CONFORMANCE_COMMIT, + }, + "implementation": { + "commit": implementation_commit, + "sdk_version": package_json["devDependencies"]["@modelcontextprotocol/sdk"], + }, + "versions": { + "python": sys.version.split()[0], + "node": node_version, + "npm": npm_version, + }, + "inventory": { + "certified": { + "server": [entry["scenario"] for entry in SERVER_SCENARIOS], + "client": [entry["scenario"] for entry in CLIENT_SCENARIOS], + }, + "exclusions": EXCLUSIONS, + }, + "runs": runs, + } + + +def run_all() -> dict[str, Any]: + records: list[dict[str, Any]] = [] + for config in SERVER_SCENARIOS: + records.append(_run_server_scenario(dict(config))) + for config in CLIENT_SCENARIOS: + records.append(_run_client_scenario(dict(config))) + return build_receipt(records) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--receipt", type=Path, default=DEFAULT_RECEIPT) + args = parser.parse_args() + + receipt = run_all() + args.receipt.parent.mkdir(parents=True, exist_ok=True) + args.receipt.write_text(json.dumps(receipt, indent=2) + "\n") + return 0 if receipt["overall_ok"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/fixtures/mcp_conformance/official-2026-07-28-receipt.json b/tests/fixtures/mcp_conformance/official-2026-07-28-receipt.json new file mode 100644 index 000000000..c84f15a85 --- /dev/null +++ b/tests/fixtures/mcp_conformance/official-2026-07-28-receipt.json @@ -0,0 +1,3627 @@ +{ + "schema_version": "eventrelay.mcp-conformance-receipt.v1", + "baseline_revision": "2026-07-28", + "generated_at": "2026-09-08T23:52:06Z", + "overall_ok": true, + "conformance": { + "package": "git+https://github.com/modelcontextprotocol/conformance.git#a983ba93c91e0bb31d0b6849eeb52f0ad1083107", + "commit": "a983ba93c91e0bb31d0b6849eeb52f0ad1083107" + }, + "implementation": { + "commit": "476260e5ef48c00c2d33f5b2444b556f42d71100", + "sdk_version": "^1.30.0" + }, + "versions": { + "python": "3.12.3", + "node": "v24.19.0", + "npm": "11.17.0" + }, + "inventory": { + "certified": { + "server": [ + "tools-list", + "tools-call-simple-text", + "tools-call-error", + "server-initialize" + ], + "client": [ + "auth/metadata-var2", + "auth/token-endpoint-auth-basic", + "auth/token-endpoint-auth-post", + "auth/token-endpoint-auth-none" + ] + }, + "exclusions": { + "server": [ + { + "reason": "surface-not-implemented", + "scenarios": [ + "server-stateless", + "completion-complete", + "tools-call-image", + "tools-call-audio", + "tools-call-embedded-resource", + "tools-call-mixed-content", + "tools-call-with-progress", + "server-sse-multiple-streams", + "resources-list", + "resources-read-text", + "resources-read-binary", + "resources-templates-read", + "sep-2164-resource-not-found", + "prompts-list", + "prompts-get-simple", + "prompts-get-with-args", + "prompts-get-embedded-resource", + "prompts-get-with-image", + "dns-rebinding-protection", + "caching", + "input-required-result-basic-elicitation", + "input-required-result-basic-sampling", + "input-required-result-basic-list-roots", + "input-required-result-request-state", + "input-required-result-multiple-input-requests", + "input-required-result-multi-round", + "input-required-result-missing-input-response", + "input-required-result-non-tool-request", + "input-required-result-result-type", + "input-required-result-unsupported-methods", + "input-required-result-tampered-state", + "input-required-result-capability-check", + "input-required-result-ignore-extra-params", + "input-required-result-validate-input" + ] + }, + { + "reason": "extension-not-implemented", + "scenarios": [ + "tasks-lifecycle", + "tasks-capability-negotiation", + "tasks-wire-fields", + "tasks-request-state-removal", + "tasks-mrtr-input", + "tasks-request-headers", + "tasks-dispatch-and-envelope", + "tasks-status-notifications", + "tasks-required-task-error", + "tasks-mrtr-composition" + ] + } + ], + "client": [ + { + "reason": "surface-not-certified-in-this-baseline", + "scenarios": [ + "tools_call", + "request-metadata", + "auth/metadata-default", + "auth/metadata-var1", + "auth/metadata-var3", + "auth/basic-cimd", + "auth/scope-from-www-authenticate", + "auth/scope-from-scopes-supported", + "auth/scope-omitted-when-undefined", + "auth/scope-step-up", + "auth/scope-retry-limit", + "auth/pre-registration", + "auth/resource-mismatch", + "auth/offline-access-scope", + "auth/offline-access-not-supported", + "auth/authorization-server-migration", + "auth/iss-supported", + "auth/iss-not-advertised", + "auth/iss-supported-missing", + "auth/iss-wrong-issuer", + "auth/iss-unexpected", + "auth/iss-normalized", + "auth/metadata-issuer-mismatch", + "sep-2322-client-request-state", + "http-standard-headers", + "http-custom-headers", + "http-invalid-tool-headers", + "json-schema-ref-no-deref" + ] + }, + { + "reason": "extension-not-implemented", + "scenarios": [ + "auth/client-credentials-jwt", + "auth/client-credentials-basic", + "auth/enterprise-managed-authorization", + "auth/dpop", + "auth/dpop-nonce", + "auth/wif-jwt-bearer", + "json-schema-2020-12-preservation" + ] + } + ] + } + }, + "runs": [ + { + "leg": "server", + "scenario": "tools-list", + "spec_version": "2026-07-28", + "required": true, + "exit_code": 0, + "summary": { + "ok": true, + "counts": { + "SUCCESS": 4 + }, + "blocking": [] + }, + "warnings": [], + "failures": [], + "checks": [ + { + "id": "tools-list", + "name": "ToolsList", + "description": "Server lists available tools with valid structure", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:17.472Z", + "specReferences": [ + { + "id": "MCP-Tools-List", + "url": "https://modelcontextprotocol.io/specification/2025-06-18/server/tools#listing-tools" + } + ], + "details": { + "toolCount": 2, + "tools": [ + "test_simple_text", + "test_error_handling" + ] + } + }, + { + "id": "tools-name-format", + "name": "ToolsNameFormat", + "description": "Tool names SHOULD be 1-128 characters and match ^[A-Za-z0-9_.-]+$", + "specReferences": [ + { + "id": "MCP-Tool-Names", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-names" + }, + { + "id": "MCP-Tool-Names-Draft", + "url": "https://modelcontextprotocol.io/specification/draft/server/tools#tool-names" + }, + { + "id": "SEP-986-History", + "url": "https://github.com/modelcontextprotocol/modelcontextprotocol/issues/986" + }, + { + "id": "SEP-986-Spec-Integration", + "url": "https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1603" + } + ], + "timestamp": "2026-09-08T23:51:17.473Z", + "status": "SUCCESS", + "details": { + "toolCount": 2, + "results": { + "test_simple_text": "valid", + "test_error_handling": "valid" + } + } + }, + { + "id": "tools-list-deterministic-order", + "name": "ToolsListDeterministicOrder", + "description": "Consecutive tools/list requests return the same tools in the same order", + "specReferences": [ + { + "id": "MCP-Tools-Deterministic-Order", + "url": "https://modelcontextprotocol.io/specification/2026-07-28/server/tools#capabilities" + } + ], + "source": { + "introducedIn": "2026-07-28" + }, + "timestamp": "2026-09-08T23:51:17.520Z", + "status": "SUCCESS", + "details": { + "toolCount": 2, + "probes": 3, + "orders": [ + [ + "test_simple_text", + "test_error_handling" + ], + [ + "test_simple_text", + "test_error_handling" + ], + [ + "test_simple_text", + "test_error_handling" + ] + ] + } + }, + { + "id": "wire-schema-valid", + "name": "WireSchemaValid", + "description": "Every JSON-RPC message the implementation sent is valid per the spec JSON schema for the negotiated spec version", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:17.521Z", + "specReferences": [ + { + "id": "MCP-Schema", + "url": "https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.json" + } + ], + "details": { + "messagesValidated": 6, + "violations": [] + } + } + ], + "stdout": "Running client scenario 'tools-list' against server: http://127.0.0.1:48389/mcp\nResults saved to /tmp/mcp-conformance-server-ao46vvb6/server-tools-list-2026-09-08T23-51-17-371Z\nChecks:\n\u001b[90m2026-09-08T23:51:17.472Z\u001b[0m [tools-list ] \u001b[32mSUCCESS\u001b[0m Server lists available tools with valid structure\n\u001b[90m2026-09-08T23:51:17.473Z\u001b[0m [tools-name-format ] \u001b[32mSUCCESS\u001b[0m Tool names SHOULD be 1-128 characters and match ^[A-Za-z0-9_.-]+$\n\u001b[90m2026-09-08T23:51:17.520Z\u001b[0m [tools-list-deterministic-order] \u001b[32mSUCCESS\u001b[0m Consecutive tools/list requests return the same tools in the same order\n\u001b[90m2026-09-08T23:51:17.521Z\u001b[0m [wire-schema-valid ] \u001b[32mSUCCESS\u001b[0m Every JSON-RPC message the implementation sent is valid per the spec JSON schema for the negotiated spec version\n\nTest Results:\nPassed: 4/4, 0 failed, 0 warnings\n", + "stderr": "" + }, + { + "leg": "server", + "scenario": "tools-call-simple-text", + "spec_version": "2026-07-28", + "required": true, + "exit_code": 0, + "summary": { + "ok": true, + "counts": { + "SUCCESS": 2 + }, + "blocking": [] + }, + "warnings": [], + "failures": [], + "checks": [ + { + "id": "tools-call-simple-text", + "name": "ToolsCallSimpleText", + "description": "Tool returns simple text content", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:24.372Z", + "specReferences": [ + { + "id": "MCP-Tools-Call", + "url": "https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools" + } + ], + "details": { + "result": { + "resultType": "complete", + "ttlMs": 0, + "cacheScope": "private", + "content": [ + { + "type": "text", + "text": "This is a simple text response for testing." + } + ] + } + } + }, + { + "id": "wire-schema-valid", + "name": "WireSchemaValid", + "description": "Every JSON-RPC message the implementation sent is valid per the spec JSON schema for the negotiated spec version", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:24.372Z", + "specReferences": [ + { + "id": "MCP-Schema", + "url": "https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.json" + } + ], + "details": { + "messagesValidated": 2, + "violations": [] + } + } + ], + "stdout": "Running client scenario 'tools-call-simple-text' against server: http://127.0.0.1:57175/mcp\nResults saved to /tmp/mcp-conformance-server-lmx0h254/server-tools-call-simple-text-2026-09-08T23-51-24-249Z\nChecks:\n\u001b[90m2026-09-08T23:51:24.372Z\u001b[0m [tools-call-simple-text] \u001b[32mSUCCESS\u001b[0m Tool returns simple text content\n\u001b[90m2026-09-08T23:51:24.372Z\u001b[0m [wire-schema-valid ] \u001b[32mSUCCESS\u001b[0m Every JSON-RPC message the implementation sent is valid per the spec JSON schema for the negotiated spec version\n\nTest Results:\nPassed: 2/2, 0 failed, 0 warnings\n", + "stderr": "" + }, + { + "leg": "server", + "scenario": "tools-call-error", + "spec_version": "2026-07-28", + "required": true, + "exit_code": 0, + "summary": { + "ok": true, + "counts": { + "SUCCESS": 2 + }, + "blocking": [] + }, + "warnings": [], + "failures": [], + "checks": [ + { + "id": "tools-call-error", + "name": "ToolsCallError", + "description": "Tool returns error correctly", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:31.321Z", + "specReferences": [ + { + "id": "MCP-Error-Handling", + "url": "https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle" + } + ], + "details": { + "result": { + "resultType": "complete", + "ttlMs": 0, + "cacheScope": "private", + "isError": true, + "content": [ + { + "type": "text", + "text": "This tool intentionally returns an error for testing" + } + ] + } + } + }, + { + "id": "wire-schema-valid", + "name": "WireSchemaValid", + "description": "Every JSON-RPC message the implementation sent is valid per the spec JSON schema for the negotiated spec version", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:31.321Z", + "specReferences": [ + { + "id": "MCP-Schema", + "url": "https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.json" + } + ], + "details": { + "messagesValidated": 2, + "violations": [] + } + } + ], + "stdout": "Running client scenario 'tools-call-error' against server: http://127.0.0.1:53465/mcp\nResults saved to /tmp/mcp-conformance-server-xcde4dr8/server-tools-call-error-2026-09-08T23-51-31-196Z\nChecks:\n\u001b[90m2026-09-08T23:51:31.321Z\u001b[0m [tools-call-error ] \u001b[32mSUCCESS\u001b[0m Tool returns error correctly\n\u001b[90m2026-09-08T23:51:31.321Z\u001b[0m [wire-schema-valid] \u001b[32mSUCCESS\u001b[0m Every JSON-RPC message the implementation sent is valid per the spec JSON schema for the negotiated spec version\n\nTest Results:\nPassed: 2/2, 0 failed, 0 warnings\n", + "stderr": "" + }, + { + "leg": "server", + "scenario": "server-initialize", + "spec_version": "2025-11-25", + "required": true, + "exit_code": 0, + "summary": { + "ok": true, + "counts": { + "SUCCESS": 2, + "INFO": 1 + }, + "blocking": [] + }, + "warnings": [], + "failures": [], + "checks": [ + { + "id": "server-initialize", + "name": "ServerInitialize", + "description": "Server responds to initialize request with valid structure", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:38.274Z", + "specReferences": [ + { + "id": "MCP-Initialize", + "url": "https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization" + } + ], + "details": { + "serverUrl": "http://127.0.0.1:56021/mcp", + "connected": true + } + }, + { + "id": "server-session-id-visible-ascii", + "name": "ServerSessionIdVisibleAscii", + "description": "Server-provided session ID uses only visible ASCII characters", + "status": "INFO", + "timestamp": "2026-09-08T23:51:38.280Z", + "specReferences": [ + { + "id": "MCP-Session-Management", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#session-management" + } + ], + "details": { + "message": "Server did not provide an MCP-Session-Id header (session ID is optional)" + } + }, + { + "id": "wire-schema-valid", + "name": "WireSchemaValid", + "description": "Every JSON-RPC message the implementation sent is valid per the spec JSON schema for the negotiated spec version", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:38.280Z", + "specReferences": [ + { + "id": "MCP-Schema", + "url": "https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-11-25/schema.json" + } + ], + "details": { + "messagesValidated": 3, + "violations": [] + } + } + ], + "stdout": "Running client scenario 'server-initialize' against server: http://127.0.0.1:56021/mcp\nResults saved to /tmp/mcp-conformance-server-wr_g595t/server-server-initialize-2026-09-08T23-51-38-160Z\nChecks:\n\u001b[90m2026-09-08T23:51:38.274Z\u001b[0m [server-initialize ] \u001b[32mSUCCESS\u001b[0m Server responds to initialize request with valid structure\n\u001b[90m2026-09-08T23:51:38.280Z\u001b[0m [server-session-id-visible-ascii] \u001b[36mINFO \u001b[0m Server-provided session ID uses only visible ASCII characters\n\u001b[90m2026-09-08T23:51:38.280Z\u001b[0m [wire-schema-valid ] \u001b[32mSUCCESS\u001b[0m Every JSON-RPC message the implementation sent is valid per the spec JSON schema for the negotiated spec version\n\nTest Results:\nPassed: 2/2, 0 failed, 0 warnings\n", + "stderr": "" + }, + { + "leg": "client", + "scenario": "auth/metadata-var2", + "spec_version": "2026-07-28", + "required": true, + "exit_code": 1, + "summary": { + "ok": true, + "counts": { + "INFO": 22, + "SUCCESS": 18 + }, + "blocking": [] + }, + "warnings": [], + "failures": [], + "checks": [ + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received POST request for /mcp (method: initialize)", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.353Z", + "details": { + "method": "POST", + "path": "/mcp", + "body": { + "method": "initialize", + "params": { + "protocolVersion": "2025-11-25", + "capabilities": {}, + "clientInfo": { + "name": "eventrelay-conformance-client", + "version": "1.0.0" + } + }, + "jsonrpc": "2.0", + "id": 0 + }, + "mcpMethod": "initialize" + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 401 response for POST /mcp (method: initialize)", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.355Z", + "details": { + "method": "POST", + "path": "/mcp", + "statusCode": 401, + "mcpMethod": "initialize", + "headers": { + "x-powered-by": "Express", + "www-authenticate": "Bearer error=\"invalid_token\", error_description=\"Missing Authorization header\"", + "content-type": "application/json; charset=utf-8", + "content-length": "76", + "etag": "W/\"4c-ptrIdu+3yjAtarglCEu6XVLnz2c\"" + }, + "body": { + "error": "invalid_token", + "error_description": "Missing Authorization header" + } + } + }, + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received GET request for /.well-known/oauth-protected-resource/mcp", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.364Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp" + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 404 response for GET /.well-known/oauth-protected-resource/mcp", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.365Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp", + "statusCode": 404, + "headers": { + "x-powered-by": "Express", + "content-security-policy": "default-src 'none'", + "x-content-type-options": "nosniff", + "content-type": "text/html; charset=utf-8", + "content-length": 179 + }, + "body": "\n\n\n\nError\n\n\n
Cannot GET /.well-known/oauth-protected-resource/mcp
\n\n\n" + } + }, + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received GET request for /.well-known/oauth-protected-resource", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.367Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource" + } + }, + { + "id": "prm-pathbased-requested", + "name": "PRMPathBasedRequested", + "description": "Client requested PRM metadata at path-based location", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.367Z", + "specReferences": [ + { + "id": "RFC-9728", + "url": "https://www.rfc-editor.org/rfc/rfc9728.html#section-3.1" + }, + { + "id": "MCP-2025-06-18-PRM-discovery", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#protected-resource-metadata-discovery-requirements" + } + ], + "details": { + "url": "/.well-known/oauth-protected-resource", + "path": "/.well-known/oauth-protected-resource" + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 200 response for GET /.well-known/oauth-protected-resource", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.367Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "96", + "etag": "W/\"60-jXhMHvEX+PZGQ4I80/MZXGEOo5M\"" + }, + "body": { + "resource": "http://localhost:44289", + "authorization_servers": [ + "http://localhost:37107/tenant1" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received GET request for /.well-known/oauth-authorization-server/tenant1", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.375Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-authorization-server/tenant1" + } + }, + { + "id": "authorization-server-metadata", + "name": "AuthorizationServerMetadata", + "description": "Client requested authorization server metadata", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.375Z", + "specReferences": [ + { + "id": "RFC-8414-metadata-request", + "url": "https://www.rfc-editor.org/rfc/rfc8414.html#section-3.1" + }, + { + "id": "MCP-Authorization-metadata-discovery", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-server-metadata-discovery" + } + ], + "details": { + "url": "/.well-known/oauth-authorization-server/tenant1", + "path": "/.well-known/oauth-authorization-server/tenant1" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 200 response for GET /.well-known/oauth-authorization-server/tenant1", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.375Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-authorization-server/tenant1", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "479", + "etag": "W/\"1df-Evn1/r0opr/ITU52Q3op8J9booM\"" + }, + "body": { + "issuer": "http://localhost:37107/tenant1", + "authorization_endpoint": "http://localhost:37107/tenant1/authorize", + "token_endpoint": "http://localhost:37107/tenant1/token", + "registration_endpoint": "http://localhost:37107/tenant1/register", + "response_types_supported": [ + "code" + ], + "grant_types_supported": [ + "authorization_code", + "refresh_token" + ], + "code_challenge_methods_supported": [ + "S256" + ], + "authorization_response_iss_parameter_supported": true, + "token_endpoint_auth_methods_supported": [ + "none" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received POST request for /tenant1/register", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.381Z", + "details": { + "method": "POST", + "path": "/tenant1/register", + "body": { + "client_name": "eventrelay-conformance-client", + "redirect_uris": [ + "http://localhost:3000/callback" + ], + "application_type": "native" + } + } + }, + { + "id": "client-registration", + "name": "ClientRegistration", + "description": "Client registered with authorization server", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.381Z", + "specReferences": [ + { + "id": "MCP-Dynamic-client-registration", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/client#dynamic-client-registration" + } + ], + "details": { + "endpoint": "/register", + "clientName": "eventrelay-conformance-client" + } + }, + { + "id": "sep-837-application-type-present", + "name": "DCR application_type specified", + "description": "Client specified application_type \"native\" during Dynamic Client Registration", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.381Z", + "specReferences": [ + { + "id": "MCP-Dynamic-client-registration", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/client#dynamic-client-registration" + } + ], + "details": { + "application_type": "native" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 201 response for POST /tenant1/register", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.381Z", + "details": { + "method": "POST", + "path": "/tenant1/register", + "statusCode": 201, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "164", + "etag": "W/\"a4-TMdgwr0j2yD0GKCOcPKDUR3xJxc\"" + }, + "body": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "client_name": "eventrelay-conformance-client", + "redirect_uris": [ + "http://localhost:3000/callback" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received GET request for /tenant1/authorize", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.388Z", + "details": { + "method": "GET", + "path": "/tenant1/authorize", + "query": { + "response_type": "code", + "client_id": "test-client-id", + "code_challenge": "jSi8MOZWNO4dg4ibVwZar6mqtjXDYjsgAUCM31owIxg", + "code_challenge_method": "S256", + "redirect_uri": "http://localhost:3000/callback", + "resource": "http://localhost:44289" + } + } + }, + { + "id": "authorization-request", + "name": "AuthorizationRequest", + "description": "Client made authorization request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.388Z", + "specReferences": [ + { + "id": "OAUTH-2.1-authorization-endpoint", + "url": "https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-authorization-endpoint" + } + ], + "details": { + "query": { + "response_type": "code", + "client_id": "test-client-id", + "code_challenge": "jSi8MOZWNO4dg4ibVwZar6mqtjXDYjsgAUCM31owIxg", + "code_challenge_method": "S256", + "redirect_uri": "http://localhost:3000/callback", + "resource": "http://localhost:44289" + } + } + }, + { + "id": "pkce-code-challenge-sent", + "name": "PKCE Code Challenge", + "description": "Client sent code_challenge in authorization request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.388Z", + "specReferences": [ + { + "id": "MCP-PKCE-requirement", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection" + } + ] + }, + { + "id": "pkce-s256-method-used", + "name": "PKCE S256 Method", + "description": "Client used S256 code challenge method", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.388Z", + "specReferences": [ + { + "id": "MCP-PKCE-requirement", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection" + } + ], + "details": { + "method": "S256" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 302 response for GET /tenant1/authorize", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.390Z", + "details": { + "method": "GET", + "path": "/tenant1/authorize", + "statusCode": 302, + "headers": { + "x-powered-by": "Express", + "location": "http://localhost:3000/callback?code=test-auth-code&iss=http%3A%2F%2Flocalhost%3A37107%2Ftenant1", + "vary": "Accept", + "content-type": "text/plain; charset=utf-8", + "content-length": "117" + }, + "body": "Found. Redirecting to http://localhost:3000/callback?code=test-auth-code&iss=http%3A%2F%2Flocalhost%3A37107%2Ftenant1" + } + }, + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received GET request for /.well-known/oauth-protected-resource/mcp", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.393Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp" + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 404 response for GET /.well-known/oauth-protected-resource/mcp", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.393Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp", + "statusCode": 404, + "headers": { + "x-powered-by": "Express", + "content-security-policy": "default-src 'none'", + "x-content-type-options": "nosniff", + "content-type": "text/html; charset=utf-8", + "content-length": 179 + }, + "body": "\n\n\n\nError\n\n\n
Cannot GET /.well-known/oauth-protected-resource/mcp
\n\n\n" + } + }, + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received GET request for /.well-known/oauth-protected-resource", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.396Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource" + } + }, + { + "id": "prm-pathbased-requested", + "name": "PRMPathBasedRequested", + "description": "Client requested PRM metadata at path-based location", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.396Z", + "specReferences": [ + { + "id": "RFC-9728", + "url": "https://www.rfc-editor.org/rfc/rfc9728.html#section-3.1" + }, + { + "id": "MCP-2025-06-18-PRM-discovery", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#protected-resource-metadata-discovery-requirements" + } + ], + "details": { + "url": "/.well-known/oauth-protected-resource", + "path": "/.well-known/oauth-protected-resource" + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 200 response for GET /.well-known/oauth-protected-resource", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.396Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "96", + "etag": "W/\"60-jXhMHvEX+PZGQ4I80/MZXGEOo5M\"" + }, + "body": { + "resource": "http://localhost:44289", + "authorization_servers": [ + "http://localhost:37107/tenant1" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received GET request for /.well-known/oauth-authorization-server/tenant1", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.399Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-authorization-server/tenant1" + } + }, + { + "id": "authorization-server-metadata", + "name": "AuthorizationServerMetadata", + "description": "Client requested authorization server metadata", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.399Z", + "specReferences": [ + { + "id": "RFC-8414-metadata-request", + "url": "https://www.rfc-editor.org/rfc/rfc8414.html#section-3.1" + }, + { + "id": "MCP-Authorization-metadata-discovery", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-server-metadata-discovery" + } + ], + "details": { + "url": "/.well-known/oauth-authorization-server/tenant1", + "path": "/.well-known/oauth-authorization-server/tenant1" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 200 response for GET /.well-known/oauth-authorization-server/tenant1", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.399Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-authorization-server/tenant1", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "479", + "etag": "W/\"1df-Evn1/r0opr/ITU52Q3op8J9booM\"" + }, + "body": { + "issuer": "http://localhost:37107/tenant1", + "authorization_endpoint": "http://localhost:37107/tenant1/authorize", + "token_endpoint": "http://localhost:37107/tenant1/token", + "registration_endpoint": "http://localhost:37107/tenant1/register", + "response_types_supported": [ + "code" + ], + "grant_types_supported": [ + "authorization_code", + "refresh_token" + ], + "code_challenge_methods_supported": [ + "S256" + ], + "authorization_response_iss_parameter_supported": true, + "token_endpoint_auth_methods_supported": [ + "none" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received POST request for /tenant1/token", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.406Z", + "details": { + "method": "POST", + "path": "/tenant1/token", + "body": { + "grant_type": "authorization_code", + "code": "test-auth-code", + "code_verifier": "GeP~DC2vkUouVgwmUd_Wk0U2rujkkIEnlATJ4YFxsWv", + "redirect_uri": "http://localhost:3000/callback", + "resource": "http://localhost:44289", + "client_id": "test-client-id" + } + } + }, + { + "id": "token-request", + "name": "TokenRequest", + "description": "Client requested access token", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.406Z", + "specReferences": [ + { + "id": "OAUTH-2.1-token-request", + "url": "https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-token-request" + } + ], + "details": { + "endpoint": "/token", + "grantType": "authorization_code" + } + }, + { + "id": "pkce-code-verifier-sent", + "name": "PKCE Code Verifier", + "description": "Client sent code_verifier in token request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.406Z", + "specReferences": [ + { + "id": "MCP-PKCE-requirement", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection" + } + ] + }, + { + "id": "pkce-verifier-matches-challenge", + "name": "PKCE Verifier Validation", + "description": "code_verifier correctly matches code_challenge (S256)", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.406Z", + "specReferences": [ + { + "id": "MCP-PKCE-requirement", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection" + } + ], + "details": { + "matches": true, + "storedChallenge": "jSi8MOZWNO4dg4ibVwZar6mqtjXDYjsgAUCM31owIxg", + "computedChallenge": "jSi8MOZWNO4dg4ibVwZar6mqtjXDYjsgAUCM31owIxg" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 200 response for POST /tenant1/token", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.406Z", + "details": { + "method": "POST", + "path": "/tenant1/token", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "83", + "etag": "W/\"53-NPMnJBuWFI2/kTGncSBE/PODZaU\"" + }, + "body": { + "access_token": "test-token-1788911505406", + "token_type": "Bearer", + "expires_in": 3600 + } + } + }, + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received POST request for /mcp (method: initialize)", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.411Z", + "details": { + "method": "POST", + "path": "/mcp", + "body": { + "method": "initialize", + "params": { + "protocolVersion": "2025-11-25", + "capabilities": {}, + "clientInfo": { + "name": "eventrelay-conformance-client", + "version": "1.0.0" + } + }, + "jsonrpc": "2.0", + "id": 0 + }, + "mcpMethod": "initialize" + } + }, + { + "id": "valid-bearer-token", + "name": "ValidBearerToken", + "description": "Client provided valid bearer token", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.411Z", + "specReferences": [ + { + "id": "MCP-Access-token-usage", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#access-token-usage" + } + ], + "details": { + "token": "test-token-1788...", + "scopes": [] + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 400 response for POST /mcp (method: initialize)", + "status": "INFO", + "timestamp": "2026-09-08T23:51:45.411Z", + "details": { + "method": "POST", + "path": "/mcp", + "statusCode": 400, + "mcpMethod": "initialize", + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "96", + "etag": "W/\"60-+I3pGWaBJPzWK/0N3EGEE0YM2QY\"" + }, + "body": { + "jsonrpc": "2.0", + "id": 0, + "error": { + "code": -32020, + "message": "Missing MCP-Protocol-Version header" + } + } + } + }, + { + "id": "resource-parameter-in-authorization", + "name": "Resource parameter in authorization request", + "description": "Client included resource parameter in authorization request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.432Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + } + ], + "details": { + "resource": "http://localhost:44289" + } + }, + { + "id": "resource-parameter-in-token", + "name": "Resource parameter in token request", + "description": "Client included resource parameter in token request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.432Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + } + ], + "details": { + "resource": "http://localhost:44289" + } + }, + { + "id": "resource-parameter-valid-uri", + "name": "Resource parameter is valid canonical URI", + "description": "Resource parameter is a valid canonical URI (has scheme, no fragment)", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.432Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + } + ], + "details": { + "resource": "http://localhost:44289" + } + }, + { + "id": "resource-parameter-consistency", + "name": "Resource parameter consistency", + "description": "Resource parameter is consistent between authorization and token requests", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.432Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + } + ], + "details": { + "authorizationResource": "http://localhost:44289", + "tokenResource": "http://localhost:44289" + } + }, + { + "id": "resource-parameter-matches-prm", + "name": "Resource parameter matches protected resource metadata", + "description": "Client sent the resource identifier exactly as published in protected resource metadata", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:45.432Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + }, + { + "id": "MCP-Canonical-Server-URI", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#canonical-server-uri" + }, + { + "id": "RFC-9728-resource-identity", + "url": "https://www.rfc-editor.org/rfc/rfc9728.html#section-3.3" + } + ], + "details": { + "prmResource": "http://localhost:44289", + "authorizationResource": "http://localhost:44289", + "tokenResource": "http://localhost:44289" + } + } + ], + "stdout": "", + "stderr": "Starting scenario: auth/metadata-var2\nExecuting client: node /home/runner/work/EventRelay/EventRelay/tests/testing/official_mcp_auth_client.mjs http://localhost:44289/mcp\n(node:10622) [DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated.\n(Use `node --trace-deprecation ...` to show where the warning was created)\n\nClient exited with code 1\n\nStderr:\nError: Streamable HTTP error: Error POSTing to endpoint: {\"jsonrpc\":\"2.0\",\"id\":0,\"error\":{\"code\":-32020,\"message\":\"Missing MCP-Protocol-Version header\"}}\n at StreamableHTTPClientTransport.send (file:///home/runner/work/EventRelay/EventRelay/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js:365:23)\n at process.processTicksAndRejections (node:internal/process/task_queues:104:5)\n\nResults saved to /tmp/mcp-conformance-client-7n8435f8/auth/metadata-var2-2026-09-08T23-51-45-105Z\nChecks:\n\u001b[90m2026-09-08T23:51:45.353Z\u001b[0m [incoming-request ] \u001b[36mINFO \u001b[0m Received POST request for /mcp (method: initialize)\n\u001b[90m2026-09-08T23:51:45.355Z\u001b[0m [outgoing-response ] \u001b[36mINFO \u001b[0m Sent 401 response for POST /mcp (method: initialize)\n\n\u001b[90m2026-09-08T23:51:45.364Z\u001b[0m [incoming-request ] \u001b[36mINFO \u001b[0m Received GET request for /.well-known/oauth-protected-resource/mcp\n\u001b[90m2026-09-08T23:51:45.365Z\u001b[0m [outgoing-response ] \u001b[36mINFO \u001b[0m Sent 404 response for GET /.well-known/oauth-protected-resource/mcp\n\n\u001b[90m2026-09-08T23:51:45.367Z\u001b[0m [incoming-request ] \u001b[36mINFO \u001b[0m Received GET request for /.well-known/oauth-protected-resource\n\u001b[90m2026-09-08T23:51:45.367Z\u001b[0m [prm-pathbased-requested ] \u001b[32mSUCCESS\u001b[0m Client requested PRM metadata at path-based location\n\u001b[90m2026-09-08T23:51:45.367Z\u001b[0m [outgoing-response ] \u001b[36mINFO \u001b[0m Sent 200 response for GET /.well-known/oauth-protected-resource\n\n\u001b[90m2026-09-08T23:51:45.375Z\u001b[0m [incoming-auth-request ] \u001b[36mINFO \u001b[0m Received GET request for /.well-known/oauth-authorization-server/tenant1\n\u001b[90m2026-09-08T23:51:45.375Z\u001b[0m [authorization-server-metadata ] \u001b[32mSUCCESS\u001b[0m Client requested authorization server metadata\n\u001b[90m2026-09-08T23:51:45.375Z\u001b[0m [outgoing-auth-response ] \u001b[36mINFO \u001b[0m Sent 200 response for GET /.well-known/oauth-authorization-server/tenant1\n\n\u001b[90m2026-09-08T23:51:45.381Z\u001b[0m [incoming-auth-request ] \u001b[36mINFO \u001b[0m Received POST request for /tenant1/register\n\u001b[90m2026-09-08T23:51:45.381Z\u001b[0m [client-registration ] \u001b[32mSUCCESS\u001b[0m Client registered with authorization server\n\u001b[90m2026-09-08T23:51:45.381Z\u001b[0m [sep-837-application-type-present ] \u001b[32mSUCCESS\u001b[0m Client specified application_type \"native\" during Dynamic Client Registration\n\u001b[90m2026-09-08T23:51:45.381Z\u001b[0m [outgoing-auth-response ] \u001b[36mINFO \u001b[0m Sent 201 response for POST /tenant1/register\n\n\u001b[90m2026-09-08T23:51:45.388Z\u001b[0m [incoming-auth-request ] \u001b[36mINFO \u001b[0m Received GET request for /tenant1/authorize\n\u001b[90m2026-09-08T23:51:45.388Z\u001b[0m [authorization-request ] \u001b[32mSUCCESS\u001b[0m Client made authorization request\n\u001b[90m2026-09-08T23:51:45.388Z\u001b[0m [pkce-code-challenge-sent ] \u001b[32mSUCCESS\u001b[0m Client sent code_challenge in authorization request\n\u001b[90m2026-09-08T23:51:45.388Z\u001b[0m [pkce-s256-method-used ] \u001b[32mSUCCESS\u001b[0m Client used S256 code challenge method\n\u001b[90m2026-09-08T23:51:45.390Z\u001b[0m [outgoing-auth-response ] \u001b[36mINFO \u001b[0m Sent 302 response for GET /tenant1/authorize\n\n\u001b[90m2026-09-08T23:51:45.393Z\u001b[0m [incoming-request ] \u001b[36mINFO \u001b[0m Received GET request for /.well-known/oauth-protected-resource/mcp\n\u001b[90m2026-09-08T23:51:45.393Z\u001b[0m [outgoing-response ] \u001b[36mINFO \u001b[0m Sent 404 response for GET /.well-\n...[truncated]" + }, + { + "leg": "client", + "scenario": "auth/token-endpoint-auth-basic", + "spec_version": "2026-07-28", + "required": true, + "exit_code": 1, + "summary": { + "ok": true, + "counts": { + "INFO": 18, + "SUCCESS": 19 + }, + "blocking": [] + }, + "warnings": [], + "failures": [], + "checks": [ + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received POST request for /mcp (method: initialize)", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.444Z", + "details": { + "method": "POST", + "path": "/mcp", + "body": { + "method": "initialize", + "params": { + "protocolVersion": "2025-11-25", + "capabilities": {}, + "clientInfo": { + "name": "eventrelay-conformance-client", + "version": "1.0.0" + } + }, + "jsonrpc": "2.0", + "id": 0 + }, + "mcpMethod": "initialize" + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 401 response for POST /mcp (method: initialize)", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.446Z", + "details": { + "method": "POST", + "path": "/mcp", + "statusCode": 401, + "mcpMethod": "initialize", + "headers": { + "x-powered-by": "Express", + "www-authenticate": "Bearer error=\"invalid_token\", error_description=\"Missing Authorization header\", resource_metadata=\"http://localhost:39749/.well-known/oauth-protected-resource/mcp\"", + "content-type": "application/json; charset=utf-8", + "content-length": "76", + "etag": "W/\"4c-ptrIdu+3yjAtarglCEu6XVLnz2c\"" + }, + "body": { + "error": "invalid_token", + "error_description": "Missing Authorization header" + } + } + }, + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received GET request for /.well-known/oauth-protected-resource/mcp", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.455Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp" + } + }, + { + "id": "prm-pathbased-requested", + "name": "PRMPathBasedRequested", + "description": "Client requested PRM metadata at path-based location", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.455Z", + "specReferences": [ + { + "id": "RFC-9728", + "url": "https://www.rfc-editor.org/rfc/rfc9728.html#section-3.1" + }, + { + "id": "MCP-2025-06-18-PRM-discovery", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#protected-resource-metadata-discovery-requirements" + } + ], + "details": { + "url": "/.well-known/oauth-protected-resource/mcp", + "path": "/.well-known/oauth-protected-resource/mcp" + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 200 response for GET /.well-known/oauth-protected-resource/mcp", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.456Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "92", + "etag": "W/\"5c-cCdQ1TyFCxDv12q1YWdFACKN+6c\"" + }, + "body": { + "resource": "http://localhost:39749/mcp", + "authorization_servers": [ + "http://localhost:43743" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received GET request for /.well-known/oauth-authorization-server", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.463Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-authorization-server" + } + }, + { + "id": "authorization-server-metadata", + "name": "AuthorizationServerMetadata", + "description": "Client requested authorization server metadata", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.464Z", + "specReferences": [ + { + "id": "RFC-8414-metadata-request", + "url": "https://www.rfc-editor.org/rfc/rfc8414.html#section-3.1" + }, + { + "id": "MCP-Authorization-metadata-discovery", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-server-metadata-discovery" + } + ], + "details": { + "url": "/.well-known/oauth-authorization-server", + "path": "/.well-known/oauth-authorization-server" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 200 response for GET /.well-known/oauth-authorization-server", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.464Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-authorization-server", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "462", + "etag": "W/\"1ce-YFhJJH2dWsMhjJ+jVcYDEh+XnuY\"" + }, + "body": { + "issuer": "http://localhost:43743", + "authorization_endpoint": "http://localhost:43743/authorize", + "token_endpoint": "http://localhost:43743/token", + "registration_endpoint": "http://localhost:43743/register", + "response_types_supported": [ + "code" + ], + "grant_types_supported": [ + "authorization_code", + "refresh_token" + ], + "code_challenge_methods_supported": [ + "S256" + ], + "authorization_response_iss_parameter_supported": true, + "token_endpoint_auth_methods_supported": [ + "client_secret_basic" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received POST request for /register", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.469Z", + "details": { + "method": "POST", + "path": "/register", + "body": { + "client_name": "eventrelay-conformance-client", + "redirect_uris": [ + "http://localhost:3000/callback" + ], + "application_type": "native" + } + } + }, + { + "id": "client-registration", + "name": "ClientRegistration", + "description": "Client registered with authorization server", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.469Z", + "specReferences": [ + { + "id": "MCP-Dynamic-client-registration", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/client#dynamic-client-registration" + } + ], + "details": { + "endpoint": "/register", + "clientName": "eventrelay-conformance-client", + "tokenEndpointAuthMethod": "client_secret_basic" + } + }, + { + "id": "sep-837-application-type-present", + "name": "DCR application_type specified", + "description": "Client specified application_type \"native\" during Dynamic Client Registration", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.469Z", + "specReferences": [ + { + "id": "MCP-Dynamic-client-registration", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/client#dynamic-client-registration" + } + ], + "details": { + "application_type": "native" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 201 response for POST /register", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.469Z", + "details": { + "method": "POST", + "path": "/register", + "statusCode": 201, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "233", + "etag": "W/\"e9-fmquPNg6IVD5Enw9g2ddMfRZFSU\"" + }, + "body": { + "client_id": "test-client-1788911512469", + "client_secret": "test-secret-1788911512469", + "client_name": "eventrelay-conformance-client", + "redirect_uris": [ + "http://localhost:3000/callback" + ], + "token_endpoint_auth_method": "client_secret_basic" + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received GET request for /authorize", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.476Z", + "details": { + "method": "GET", + "path": "/authorize", + "query": { + "response_type": "code", + "client_id": "test-client-1788911512469", + "code_challenge": "q8XslQR1t3SnTeMH1oqDFUntILGmIfzlt9SC_1SduRI", + "code_challenge_method": "S256", + "redirect_uri": "http://localhost:3000/callback", + "resource": "http://localhost:39749/mcp" + } + } + }, + { + "id": "authorization-request", + "name": "AuthorizationRequest", + "description": "Client made authorization request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.477Z", + "specReferences": [ + { + "id": "OAUTH-2.1-authorization-endpoint", + "url": "https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-authorization-endpoint" + } + ], + "details": { + "query": { + "response_type": "code", + "client_id": "test-client-1788911512469", + "code_challenge": "q8XslQR1t3SnTeMH1oqDFUntILGmIfzlt9SC_1SduRI", + "code_challenge_method": "S256", + "redirect_uri": "http://localhost:3000/callback", + "resource": "http://localhost:39749/mcp" + } + } + }, + { + "id": "pkce-code-challenge-sent", + "name": "PKCE Code Challenge", + "description": "Client sent code_challenge in authorization request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.477Z", + "specReferences": [ + { + "id": "MCP-PKCE-requirement", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection" + } + ] + }, + { + "id": "pkce-s256-method-used", + "name": "PKCE S256 Method", + "description": "Client used S256 code challenge method", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.477Z", + "specReferences": [ + { + "id": "MCP-PKCE-requirement", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection" + } + ], + "details": { + "method": "S256" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 302 response for GET /authorize", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.479Z", + "details": { + "method": "GET", + "path": "/authorize", + "statusCode": 302, + "headers": { + "x-powered-by": "Express", + "location": "http://localhost:3000/callback?code=test-auth-code&iss=http%3A%2F%2Flocalhost%3A43743", + "vary": "Accept", + "content-type": "text/plain; charset=utf-8", + "content-length": "107" + }, + "body": "Found. Redirecting to http://localhost:3000/callback?code=test-auth-code&iss=http%3A%2F%2Flocalhost%3A43743" + } + }, + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received GET request for /.well-known/oauth-protected-resource/mcp", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.482Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp" + } + }, + { + "id": "prm-pathbased-requested", + "name": "PRMPathBasedRequested", + "description": "Client requested PRM metadata at path-based location", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.482Z", + "specReferences": [ + { + "id": "RFC-9728", + "url": "https://www.rfc-editor.org/rfc/rfc9728.html#section-3.1" + }, + { + "id": "MCP-2025-06-18-PRM-discovery", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#protected-resource-metadata-discovery-requirements" + } + ], + "details": { + "url": "/.well-known/oauth-protected-resource/mcp", + "path": "/.well-known/oauth-protected-resource/mcp" + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 200 response for GET /.well-known/oauth-protected-resource/mcp", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.482Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "92", + "etag": "W/\"5c-cCdQ1TyFCxDv12q1YWdFACKN+6c\"" + }, + "body": { + "resource": "http://localhost:39749/mcp", + "authorization_servers": [ + "http://localhost:43743" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received GET request for /.well-known/oauth-authorization-server", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.484Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-authorization-server" + } + }, + { + "id": "authorization-server-metadata", + "name": "AuthorizationServerMetadata", + "description": "Client requested authorization server metadata", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.484Z", + "specReferences": [ + { + "id": "RFC-8414-metadata-request", + "url": "https://www.rfc-editor.org/rfc/rfc8414.html#section-3.1" + }, + { + "id": "MCP-Authorization-metadata-discovery", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-server-metadata-discovery" + } + ], + "details": { + "url": "/.well-known/oauth-authorization-server", + "path": "/.well-known/oauth-authorization-server" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 200 response for GET /.well-known/oauth-authorization-server", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.484Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-authorization-server", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "462", + "etag": "W/\"1ce-YFhJJH2dWsMhjJ+jVcYDEh+XnuY\"" + }, + "body": { + "issuer": "http://localhost:43743", + "authorization_endpoint": "http://localhost:43743/authorize", + "token_endpoint": "http://localhost:43743/token", + "registration_endpoint": "http://localhost:43743/register", + "response_types_supported": [ + "code" + ], + "grant_types_supported": [ + "authorization_code", + "refresh_token" + ], + "code_challenge_methods_supported": [ + "S256" + ], + "authorization_response_iss_parameter_supported": true, + "token_endpoint_auth_methods_supported": [ + "client_secret_basic" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received POST request for /token", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.489Z", + "details": { + "method": "POST", + "path": "/token", + "body": { + "grant_type": "authorization_code", + "code": "test-auth-code", + "code_verifier": "spPggNypMXCw~msQY15tEiUa6LbdVSbG2tjPFtr-OE1", + "redirect_uri": "http://localhost:3000/callback", + "resource": "http://localhost:39749/mcp" + } + } + }, + { + "id": "token-request", + "name": "TokenRequest", + "description": "Client requested access token", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.490Z", + "specReferences": [ + { + "id": "OAUTH-2.1-token-request", + "url": "https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-token-request" + } + ], + "details": { + "endpoint": "/token", + "grantType": "authorization_code" + } + }, + { + "id": "pkce-code-verifier-sent", + "name": "PKCE Code Verifier", + "description": "Client sent code_verifier in token request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.490Z", + "specReferences": [ + { + "id": "MCP-PKCE-requirement", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection" + } + ] + }, + { + "id": "pkce-verifier-matches-challenge", + "name": "PKCE Verifier Validation", + "description": "code_verifier correctly matches code_challenge (S256)", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.490Z", + "specReferences": [ + { + "id": "MCP-PKCE-requirement", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection" + } + ], + "details": { + "matches": true, + "storedChallenge": "q8XslQR1t3SnTeMH1oqDFUntILGmIfzlt9SC_1SduRI", + "computedChallenge": "q8XslQR1t3SnTeMH1oqDFUntILGmIfzlt9SC_1SduRI" + } + }, + { + "id": "token-endpoint-auth-method", + "name": "Token endpoint authentication method", + "description": "Client correctly used HTTP Basic authentication (client_secret_basic) for token endpoint", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.490Z", + "specReferences": [ + { + "id": "OAUTH-2.1-token-request", + "url": "https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-token-request" + } + ], + "details": { + "expectedAuthMethod": "client_secret_basic", + "actualAuthMethod": "client_secret_basic", + "hasAuthorizationHeader": true, + "hasBodyClientSecret": false + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 200 response for POST /token", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.490Z", + "details": { + "method": "POST", + "path": "/token", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "83", + "etag": "W/\"53-JvMktWoKtaugCcn4dR0yqNkHcQ0\"" + }, + "body": { + "access_token": "test-token-1788911512490", + "token_type": "Bearer", + "expires_in": 3600 + } + } + }, + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received POST request for /mcp (method: initialize)", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.494Z", + "details": { + "method": "POST", + "path": "/mcp", + "body": { + "method": "initialize", + "params": { + "protocolVersion": "2025-11-25", + "capabilities": {}, + "clientInfo": { + "name": "eventrelay-conformance-client", + "version": "1.0.0" + } + }, + "jsonrpc": "2.0", + "id": 0 + }, + "mcpMethod": "initialize" + } + }, + { + "id": "valid-bearer-token", + "name": "ValidBearerToken", + "description": "Client provided valid bearer token", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.494Z", + "specReferences": [ + { + "id": "MCP-Access-token-usage", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#access-token-usage" + } + ], + "details": { + "token": "test-token-1788...", + "scopes": [] + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 400 response for POST /mcp (method: initialize)", + "status": "INFO", + "timestamp": "2026-09-08T23:51:52.495Z", + "details": { + "method": "POST", + "path": "/mcp", + "statusCode": 400, + "mcpMethod": "initialize", + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "96", + "etag": "W/\"60-+I3pGWaBJPzWK/0N3EGEE0YM2QY\"" + }, + "body": { + "jsonrpc": "2.0", + "id": 0, + "error": { + "code": -32020, + "message": "Missing MCP-Protocol-Version header" + } + } + } + }, + { + "id": "resource-parameter-in-authorization", + "name": "Resource parameter in authorization request", + "description": "Client included resource parameter in authorization request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.518Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + } + ], + "details": { + "resource": "http://localhost:39749/mcp" + } + }, + { + "id": "resource-parameter-in-token", + "name": "Resource parameter in token request", + "description": "Client included resource parameter in token request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.518Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + } + ], + "details": { + "resource": "http://localhost:39749/mcp" + } + }, + { + "id": "resource-parameter-valid-uri", + "name": "Resource parameter is valid canonical URI", + "description": "Resource parameter is a valid canonical URI (has scheme, no fragment)", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.518Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + } + ], + "details": { + "resource": "http://localhost:39749/mcp" + } + }, + { + "id": "resource-parameter-consistency", + "name": "Resource parameter consistency", + "description": "Resource parameter is consistent between authorization and token requests", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.518Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + } + ], + "details": { + "authorizationResource": "http://localhost:39749/mcp", + "tokenResource": "http://localhost:39749/mcp" + } + }, + { + "id": "resource-parameter-matches-prm", + "name": "Resource parameter matches protected resource metadata", + "description": "Client sent the resource identifier exactly as published in protected resource metadata", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:52.518Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + }, + { + "id": "MCP-Canonical-Server-URI", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#canonical-server-uri" + }, + { + "id": "RFC-9728-resource-identity", + "url": "https://www.rfc-editor.org/rfc/rfc9728.html#section-3.3" + } + ], + "details": { + "prmResource": "http://localhost:39749/mcp", + "authorizationResource": "http://localhost:39749/mcp", + "tokenResource": "http://localhost:39749/mcp" + } + } + ], + "stdout": "", + "stderr": "Starting scenario: auth/token-endpoint-auth-basic\nExecuting client: node /home/runner/work/EventRelay/EventRelay/tests/testing/official_mcp_auth_client.mjs http://localhost:39749/mcp\n(node:10765) [DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated.\n(Use `node --trace-deprecation ...` to show where the warning was created)\n\nClient exited with code 1\n\nStderr:\nError: Streamable HTTP error: Error POSTing to endpoint: {\"jsonrpc\":\"2.0\",\"id\":0,\"error\":{\"code\":-32020,\"message\":\"Missing MCP-Protocol-Version header\"}}\n at StreamableHTTPClientTransport.send (file:///home/runner/work/EventRelay/EventRelay/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js:365:23)\n at process.processTicksAndRejections (node:internal/process/task_queues:104:5)\n\nResults saved to /tmp/mcp-conformance-client-yngiyg56/auth/token-endpoint-auth-basic-2026-09-08T23-51-52-182Z\nChecks:\n\u001b[90m2026-09-08T23:51:52.444Z\u001b[0m [incoming-request ] \u001b[36mINFO \u001b[0m Received POST request for /mcp (method: initialize)\n\u001b[90m2026-09-08T23:51:52.446Z\u001b[0m [outgoing-response ] \u001b[36mINFO \u001b[0m Sent 401 response for POST /mcp (method: initialize)\n\n\u001b[90m2026-09-08T23:51:52.455Z\u001b[0m [incoming-request ] \u001b[36mINFO \u001b[0m Received GET request for /.well-known/oauth-protected-resource/mcp\n\u001b[90m2026-09-08T23:51:52.455Z\u001b[0m [prm-pathbased-requested ] \u001b[32mSUCCESS\u001b[0m Client requested PRM metadata at path-based location\n\u001b[90m2026-09-08T23:51:52.456Z\u001b[0m [outgoing-response ] \u001b[36mINFO \u001b[0m Sent 200 response for GET /.well-known/oauth-protected-resource/mcp\n\n\u001b[90m2026-09-08T23:51:52.463Z\u001b[0m [incoming-auth-request ] \u001b[36mINFO \u001b[0m Received GET request for /.well-known/oauth-authorization-server\n\u001b[90m2026-09-08T23:51:52.464Z\u001b[0m [authorization-server-metadata ] \u001b[32mSUCCESS\u001b[0m Client requested authorization server metadata\n\u001b[90m2026-09-08T23:51:52.464Z\u001b[0m [outgoing-auth-response ] \u001b[36mINFO \u001b[0m Sent 200 response for GET /.well-known/oauth-authorization-server\n\n\u001b[90m2026-09-08T23:51:52.469Z\u001b[0m [incoming-auth-request ] \u001b[36mINFO \u001b[0m Received POST request for /register\n\u001b[90m2026-09-08T23:51:52.469Z\u001b[0m [client-registration ] \u001b[32mSUCCESS\u001b[0m Client registered with authorization server\n\u001b[90m2026-09-08T23:51:52.469Z\u001b[0m [sep-837-application-type-present ] \u001b[32mSUCCESS\u001b[0m Client specified application_type \"native\" during Dynamic Client Registration\n\u001b[90m2026-09-08T23:51:52.469Z\u001b[0m [outgoing-auth-response ] \u001b[36mINFO \u001b[0m Sent 201 response for POST /register\n\n\u001b[90m2026-09-08T23:51:52.476Z\u001b[0m [incoming-auth-request ] \u001b[36mINFO \u001b[0m Received GET request for /authorize\n\u001b[90m2026-09-08T23:51:52.477Z\u001b[0m [authorization-request ] \u001b[32mSUCCESS\u001b[0m Client made authorization request\n\u001b[90m2026-09-08T23:51:52.477Z\u001b[0m [pkce-code-challenge-sent ] \u001b[32mSUCCESS\u001b[0m Client sent code_challenge in authorization request\n\u001b[90m2026-09-08T23:51:52.477Z\u001b[0m [pkce-s256-method-used ] \u001b[32mSUCCESS\u001b[0m Client used S256 code challenge method\n\u001b[90m2026-09-08T23:51:52.479Z\u001b[0m [outgoing-auth-response ] \u001b[36mINFO \u001b[0m Sent 302 response for GET /authorize\n\n\u001b[90m2026-09-08T23:51:52.482Z\u001b[0m [incoming-request ] \u001b[36mINFO \u001b[0m Received GET request for /.well-known/oauth-protected-resource/mcp\n\u001b[90m2026-09-08T23:51:52.482Z\u001b[0m [prm-pathbased-requested ] \u001b[32mSUCCESS\u001b[0m Client requested PRM metadata at path-based location\n\u001b[90m2026-09-08T23:51:52.482Z\u001b[0m [outgoing-response ] \u001b[36mINFO \u001b[0m Sent 200 response for GET /.well-known/oauth-protected-resource/mcp\n\n\u001b[90m2026-09-08T23:51:52.484Z\u001b[0m [incoming-auth-request ] \u001b[36mINFO \u001b[0m Received GET request for /.well-known/oauth-authorization-serve\n...[truncated]" + }, + { + "leg": "client", + "scenario": "auth/token-endpoint-auth-post", + "spec_version": "2026-07-28", + "required": true, + "exit_code": 1, + "summary": { + "ok": true, + "counts": { + "INFO": 18, + "SUCCESS": 19 + }, + "blocking": [] + }, + "warnings": [], + "failures": [], + "checks": [ + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received POST request for /mcp (method: initialize)", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.522Z", + "details": { + "method": "POST", + "path": "/mcp", + "body": { + "method": "initialize", + "params": { + "protocolVersion": "2025-11-25", + "capabilities": {}, + "clientInfo": { + "name": "eventrelay-conformance-client", + "version": "1.0.0" + } + }, + "jsonrpc": "2.0", + "id": 0 + }, + "mcpMethod": "initialize" + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 401 response for POST /mcp (method: initialize)", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.524Z", + "details": { + "method": "POST", + "path": "/mcp", + "statusCode": 401, + "mcpMethod": "initialize", + "headers": { + "x-powered-by": "Express", + "www-authenticate": "Bearer error=\"invalid_token\", error_description=\"Missing Authorization header\", resource_metadata=\"http://localhost:44903/.well-known/oauth-protected-resource/mcp\"", + "content-type": "application/json; charset=utf-8", + "content-length": "76", + "etag": "W/\"4c-ptrIdu+3yjAtarglCEu6XVLnz2c\"" + }, + "body": { + "error": "invalid_token", + "error_description": "Missing Authorization header" + } + } + }, + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received GET request for /.well-known/oauth-protected-resource/mcp", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.533Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp" + } + }, + { + "id": "prm-pathbased-requested", + "name": "PRMPathBasedRequested", + "description": "Client requested PRM metadata at path-based location", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.533Z", + "specReferences": [ + { + "id": "RFC-9728", + "url": "https://www.rfc-editor.org/rfc/rfc9728.html#section-3.1" + }, + { + "id": "MCP-2025-06-18-PRM-discovery", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#protected-resource-metadata-discovery-requirements" + } + ], + "details": { + "url": "/.well-known/oauth-protected-resource/mcp", + "path": "/.well-known/oauth-protected-resource/mcp" + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 200 response for GET /.well-known/oauth-protected-resource/mcp", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.533Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "92", + "etag": "W/\"5c-Ff52gbYWSsZY4uraMJOfYWRXVIs\"" + }, + "body": { + "resource": "http://localhost:44903/mcp", + "authorization_servers": [ + "http://localhost:37677" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received GET request for /.well-known/oauth-authorization-server", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.541Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-authorization-server" + } + }, + { + "id": "authorization-server-metadata", + "name": "AuthorizationServerMetadata", + "description": "Client requested authorization server metadata", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.542Z", + "specReferences": [ + { + "id": "RFC-8414-metadata-request", + "url": "https://www.rfc-editor.org/rfc/rfc8414.html#section-3.1" + }, + { + "id": "MCP-Authorization-metadata-discovery", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-server-metadata-discovery" + } + ], + "details": { + "url": "/.well-known/oauth-authorization-server", + "path": "/.well-known/oauth-authorization-server" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 200 response for GET /.well-known/oauth-authorization-server", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.542Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-authorization-server", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "461", + "etag": "W/\"1cd-CmwH+Fbx7Lq8SCi8CWZThGfzBwY\"" + }, + "body": { + "issuer": "http://localhost:37677", + "authorization_endpoint": "http://localhost:37677/authorize", + "token_endpoint": "http://localhost:37677/token", + "registration_endpoint": "http://localhost:37677/register", + "response_types_supported": [ + "code" + ], + "grant_types_supported": [ + "authorization_code", + "refresh_token" + ], + "code_challenge_methods_supported": [ + "S256" + ], + "authorization_response_iss_parameter_supported": true, + "token_endpoint_auth_methods_supported": [ + "client_secret_post" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received POST request for /register", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.547Z", + "details": { + "method": "POST", + "path": "/register", + "body": { + "client_name": "eventrelay-conformance-client", + "redirect_uris": [ + "http://localhost:3000/callback" + ], + "application_type": "native" + } + } + }, + { + "id": "client-registration", + "name": "ClientRegistration", + "description": "Client registered with authorization server", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.547Z", + "specReferences": [ + { + "id": "MCP-Dynamic-client-registration", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/client#dynamic-client-registration" + } + ], + "details": { + "endpoint": "/register", + "clientName": "eventrelay-conformance-client", + "tokenEndpointAuthMethod": "client_secret_post" + } + }, + { + "id": "sep-837-application-type-present", + "name": "DCR application_type specified", + "description": "Client specified application_type \"native\" during Dynamic Client Registration", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.547Z", + "specReferences": [ + { + "id": "MCP-Dynamic-client-registration", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/client#dynamic-client-registration" + } + ], + "details": { + "application_type": "native" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 201 response for POST /register", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.548Z", + "details": { + "method": "POST", + "path": "/register", + "statusCode": 201, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "232", + "etag": "W/\"e8-Zb1cyNQM0HUP9SkBsRtwRplv2Ac\"" + }, + "body": { + "client_id": "test-client-1788911519547", + "client_secret": "test-secret-1788911519547", + "client_name": "eventrelay-conformance-client", + "redirect_uris": [ + "http://localhost:3000/callback" + ], + "token_endpoint_auth_method": "client_secret_post" + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received GET request for /authorize", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.554Z", + "details": { + "method": "GET", + "path": "/authorize", + "query": { + "response_type": "code", + "client_id": "test-client-1788911519547", + "code_challenge": "3-J7ytleHxTRF3piv7TcPOrr0tMh1mwd3tM5QJ_rSu8", + "code_challenge_method": "S256", + "redirect_uri": "http://localhost:3000/callback", + "resource": "http://localhost:44903/mcp" + } + } + }, + { + "id": "authorization-request", + "name": "AuthorizationRequest", + "description": "Client made authorization request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.555Z", + "specReferences": [ + { + "id": "OAUTH-2.1-authorization-endpoint", + "url": "https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-authorization-endpoint" + } + ], + "details": { + "query": { + "response_type": "code", + "client_id": "test-client-1788911519547", + "code_challenge": "3-J7ytleHxTRF3piv7TcPOrr0tMh1mwd3tM5QJ_rSu8", + "code_challenge_method": "S256", + "redirect_uri": "http://localhost:3000/callback", + "resource": "http://localhost:44903/mcp" + } + } + }, + { + "id": "pkce-code-challenge-sent", + "name": "PKCE Code Challenge", + "description": "Client sent code_challenge in authorization request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.555Z", + "specReferences": [ + { + "id": "MCP-PKCE-requirement", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection" + } + ] + }, + { + "id": "pkce-s256-method-used", + "name": "PKCE S256 Method", + "description": "Client used S256 code challenge method", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.555Z", + "specReferences": [ + { + "id": "MCP-PKCE-requirement", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection" + } + ], + "details": { + "method": "S256" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 302 response for GET /authorize", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.557Z", + "details": { + "method": "GET", + "path": "/authorize", + "statusCode": 302, + "headers": { + "x-powered-by": "Express", + "location": "http://localhost:3000/callback?code=test-auth-code&iss=http%3A%2F%2Flocalhost%3A37677", + "vary": "Accept", + "content-type": "text/plain; charset=utf-8", + "content-length": "107" + }, + "body": "Found. Redirecting to http://localhost:3000/callback?code=test-auth-code&iss=http%3A%2F%2Flocalhost%3A37677" + } + }, + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received GET request for /.well-known/oauth-protected-resource/mcp", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.560Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp" + } + }, + { + "id": "prm-pathbased-requested", + "name": "PRMPathBasedRequested", + "description": "Client requested PRM metadata at path-based location", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.560Z", + "specReferences": [ + { + "id": "RFC-9728", + "url": "https://www.rfc-editor.org/rfc/rfc9728.html#section-3.1" + }, + { + "id": "MCP-2025-06-18-PRM-discovery", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#protected-resource-metadata-discovery-requirements" + } + ], + "details": { + "url": "/.well-known/oauth-protected-resource/mcp", + "path": "/.well-known/oauth-protected-resource/mcp" + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 200 response for GET /.well-known/oauth-protected-resource/mcp", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.560Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "92", + "etag": "W/\"5c-Ff52gbYWSsZY4uraMJOfYWRXVIs\"" + }, + "body": { + "resource": "http://localhost:44903/mcp", + "authorization_servers": [ + "http://localhost:37677" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received GET request for /.well-known/oauth-authorization-server", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.563Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-authorization-server" + } + }, + { + "id": "authorization-server-metadata", + "name": "AuthorizationServerMetadata", + "description": "Client requested authorization server metadata", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.563Z", + "specReferences": [ + { + "id": "RFC-8414-metadata-request", + "url": "https://www.rfc-editor.org/rfc/rfc8414.html#section-3.1" + }, + { + "id": "MCP-Authorization-metadata-discovery", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-server-metadata-discovery" + } + ], + "details": { + "url": "/.well-known/oauth-authorization-server", + "path": "/.well-known/oauth-authorization-server" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 200 response for GET /.well-known/oauth-authorization-server", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.563Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-authorization-server", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "461", + "etag": "W/\"1cd-CmwH+Fbx7Lq8SCi8CWZThGfzBwY\"" + }, + "body": { + "issuer": "http://localhost:37677", + "authorization_endpoint": "http://localhost:37677/authorize", + "token_endpoint": "http://localhost:37677/token", + "registration_endpoint": "http://localhost:37677/register", + "response_types_supported": [ + "code" + ], + "grant_types_supported": [ + "authorization_code", + "refresh_token" + ], + "code_challenge_methods_supported": [ + "S256" + ], + "authorization_response_iss_parameter_supported": true, + "token_endpoint_auth_methods_supported": [ + "client_secret_post" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received POST request for /token", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.569Z", + "details": { + "method": "POST", + "path": "/token", + "body": { + "grant_type": "authorization_code", + "code": "test-auth-code", + "code_verifier": "H8.V6virw_3u9xqsVGyKnVBdVU~U4RAFK7yYqYlGT_T", + "redirect_uri": "http://localhost:3000/callback", + "resource": "http://localhost:44903/mcp", + "client_id": "test-client-1788911519547", + "client_secret": "test-secret-1788911519547" + } + } + }, + { + "id": "token-request", + "name": "TokenRequest", + "description": "Client requested access token", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.570Z", + "specReferences": [ + { + "id": "OAUTH-2.1-token-request", + "url": "https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-token-request" + } + ], + "details": { + "endpoint": "/token", + "grantType": "authorization_code" + } + }, + { + "id": "pkce-code-verifier-sent", + "name": "PKCE Code Verifier", + "description": "Client sent code_verifier in token request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.570Z", + "specReferences": [ + { + "id": "MCP-PKCE-requirement", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection" + } + ] + }, + { + "id": "pkce-verifier-matches-challenge", + "name": "PKCE Verifier Validation", + "description": "code_verifier correctly matches code_challenge (S256)", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.570Z", + "specReferences": [ + { + "id": "MCP-PKCE-requirement", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection" + } + ], + "details": { + "matches": true, + "storedChallenge": "3-J7ytleHxTRF3piv7TcPOrr0tMh1mwd3tM5QJ_rSu8", + "computedChallenge": "3-J7ytleHxTRF3piv7TcPOrr0tMh1mwd3tM5QJ_rSu8" + } + }, + { + "id": "token-endpoint-auth-method", + "name": "Token endpoint authentication method", + "description": "Client correctly used client_secret_post for token endpoint", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.570Z", + "specReferences": [ + { + "id": "OAUTH-2.1-token-request", + "url": "https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-token-request" + } + ], + "details": { + "expectedAuthMethod": "client_secret_post", + "actualAuthMethod": "client_secret_post", + "hasAuthorizationHeader": false, + "hasBodyClientSecret": true + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 200 response for POST /token", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.570Z", + "details": { + "method": "POST", + "path": "/token", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "83", + "etag": "W/\"53-BoFdhEOG1ChFJbtYNWPGwu0nC1A\"" + }, + "body": { + "access_token": "test-token-1788911519570", + "token_type": "Bearer", + "expires_in": 3600 + } + } + }, + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received POST request for /mcp (method: initialize)", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.574Z", + "details": { + "method": "POST", + "path": "/mcp", + "body": { + "method": "initialize", + "params": { + "protocolVersion": "2025-11-25", + "capabilities": {}, + "clientInfo": { + "name": "eventrelay-conformance-client", + "version": "1.0.0" + } + }, + "jsonrpc": "2.0", + "id": 0 + }, + "mcpMethod": "initialize" + } + }, + { + "id": "valid-bearer-token", + "name": "ValidBearerToken", + "description": "Client provided valid bearer token", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.574Z", + "specReferences": [ + { + "id": "MCP-Access-token-usage", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#access-token-usage" + } + ], + "details": { + "token": "test-token-1788...", + "scopes": [] + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 400 response for POST /mcp (method: initialize)", + "status": "INFO", + "timestamp": "2026-09-08T23:51:59.575Z", + "details": { + "method": "POST", + "path": "/mcp", + "statusCode": 400, + "mcpMethod": "initialize", + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "96", + "etag": "W/\"60-+I3pGWaBJPzWK/0N3EGEE0YM2QY\"" + }, + "body": { + "jsonrpc": "2.0", + "id": 0, + "error": { + "code": -32020, + "message": "Missing MCP-Protocol-Version header" + } + } + } + }, + { + "id": "resource-parameter-in-authorization", + "name": "Resource parameter in authorization request", + "description": "Client included resource parameter in authorization request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.600Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + } + ], + "details": { + "resource": "http://localhost:44903/mcp" + } + }, + { + "id": "resource-parameter-in-token", + "name": "Resource parameter in token request", + "description": "Client included resource parameter in token request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.600Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + } + ], + "details": { + "resource": "http://localhost:44903/mcp" + } + }, + { + "id": "resource-parameter-valid-uri", + "name": "Resource parameter is valid canonical URI", + "description": "Resource parameter is a valid canonical URI (has scheme, no fragment)", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.600Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + } + ], + "details": { + "resource": "http://localhost:44903/mcp" + } + }, + { + "id": "resource-parameter-consistency", + "name": "Resource parameter consistency", + "description": "Resource parameter is consistent between authorization and token requests", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.600Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + } + ], + "details": { + "authorizationResource": "http://localhost:44903/mcp", + "tokenResource": "http://localhost:44903/mcp" + } + }, + { + "id": "resource-parameter-matches-prm", + "name": "Resource parameter matches protected resource metadata", + "description": "Client sent the resource identifier exactly as published in protected resource metadata", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:51:59.600Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + }, + { + "id": "MCP-Canonical-Server-URI", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#canonical-server-uri" + }, + { + "id": "RFC-9728-resource-identity", + "url": "https://www.rfc-editor.org/rfc/rfc9728.html#section-3.3" + } + ], + "details": { + "prmResource": "http://localhost:44903/mcp", + "authorizationResource": "http://localhost:44903/mcp", + "tokenResource": "http://localhost:44903/mcp" + } + } + ], + "stdout": "", + "stderr": "Starting scenario: auth/token-endpoint-auth-post\nExecuting client: node /home/runner/work/EventRelay/EventRelay/tests/testing/official_mcp_auth_client.mjs http://localhost:44903/mcp\n(node:10907) [DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated.\n(Use `node --trace-deprecation ...` to show where the warning was created)\n\nClient exited with code 1\n\nStderr:\nError: Streamable HTTP error: Error POSTing to endpoint: {\"jsonrpc\":\"2.0\",\"id\":0,\"error\":{\"code\":-32020,\"message\":\"Missing MCP-Protocol-Version header\"}}\n at StreamableHTTPClientTransport.send (file:///home/runner/work/EventRelay/EventRelay/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js:365:23)\n at process.processTicksAndRejections (node:internal/process/task_queues:104:5)\n\nResults saved to /tmp/mcp-conformance-client-nbgsz69b/auth/token-endpoint-auth-post-2026-09-08T23-51-59-263Z\nChecks:\n\u001b[90m2026-09-08T23:51:59.522Z\u001b[0m [incoming-request ] \u001b[36mINFO \u001b[0m Received POST request for /mcp (method: initialize)\n\u001b[90m2026-09-08T23:51:59.524Z\u001b[0m [outgoing-response ] \u001b[36mINFO \u001b[0m Sent 401 response for POST /mcp (method: initialize)\n\n\u001b[90m2026-09-08T23:51:59.533Z\u001b[0m [incoming-request ] \u001b[36mINFO \u001b[0m Received GET request for /.well-known/oauth-protected-resource/mcp\n\u001b[90m2026-09-08T23:51:59.533Z\u001b[0m [prm-pathbased-requested ] \u001b[32mSUCCESS\u001b[0m Client requested PRM metadata at path-based location\n\u001b[90m2026-09-08T23:51:59.533Z\u001b[0m [outgoing-response ] \u001b[36mINFO \u001b[0m Sent 200 response for GET /.well-known/oauth-protected-resource/mcp\n\n\u001b[90m2026-09-08T23:51:59.541Z\u001b[0m [incoming-auth-request ] \u001b[36mINFO \u001b[0m Received GET request for /.well-known/oauth-authorization-server\n\u001b[90m2026-09-08T23:51:59.542Z\u001b[0m [authorization-server-metadata ] \u001b[32mSUCCESS\u001b[0m Client requested authorization server metadata\n\u001b[90m2026-09-08T23:51:59.542Z\u001b[0m [outgoing-auth-response ] \u001b[36mINFO \u001b[0m Sent 200 response for GET /.well-known/oauth-authorization-server\n\n\u001b[90m2026-09-08T23:51:59.547Z\u001b[0m [incoming-auth-request ] \u001b[36mINFO \u001b[0m Received POST request for /register\n\u001b[90m2026-09-08T23:51:59.547Z\u001b[0m [client-registration ] \u001b[32mSUCCESS\u001b[0m Client registered with authorization server\n\u001b[90m2026-09-08T23:51:59.547Z\u001b[0m [sep-837-application-type-present ] \u001b[32mSUCCESS\u001b[0m Client specified application_type \"native\" during Dynamic Client Registration\n\u001b[90m2026-09-08T23:51:59.548Z\u001b[0m [outgoing-auth-response ] \u001b[36mINFO \u001b[0m Sent 201 response for POST /register\n\n\u001b[90m2026-09-08T23:51:59.554Z\u001b[0m [incoming-auth-request ] \u001b[36mINFO \u001b[0m Received GET request for /authorize\n\u001b[90m2026-09-08T23:51:59.555Z\u001b[0m [authorization-request ] \u001b[32mSUCCESS\u001b[0m Client made authorization request\n\u001b[90m2026-09-08T23:51:59.555Z\u001b[0m [pkce-code-challenge-sent ] \u001b[32mSUCCESS\u001b[0m Client sent code_challenge in authorization request\n\u001b[90m2026-09-08T23:51:59.555Z\u001b[0m [pkce-s256-method-used ] \u001b[32mSUCCESS\u001b[0m Client used S256 code challenge method\n\u001b[90m2026-09-08T23:51:59.557Z\u001b[0m [outgoing-auth-response ] \u001b[36mINFO \u001b[0m Sent 302 response for GET /authorize\n\n\u001b[90m2026-09-08T23:51:59.560Z\u001b[0m [incoming-request ] \u001b[36mINFO \u001b[0m Received GET request for /.well-known/oauth-protected-resource/mcp\n\u001b[90m2026-09-08T23:51:59.560Z\u001b[0m [prm-pathbased-requested ] \u001b[32mSUCCESS\u001b[0m Client requested PRM metadata at path-based location\n\u001b[90m2026-09-08T23:51:59.560Z\u001b[0m [outgoing-response ] \u001b[36mINFO \u001b[0m Sent 200 response for GET /.well-known/oauth-protected-resource/mcp\n\n\u001b[90m2026-09-08T23:51:59.563Z\u001b[0m [incoming-auth-request ] \u001b[36mINFO \u001b[0m Received GET request for /.well-known/oauth-authorization-server\n\n...[truncated]" + }, + { + "leg": "client", + "scenario": "auth/token-endpoint-auth-none", + "spec_version": "2026-07-28", + "required": true, + "exit_code": 1, + "summary": { + "ok": true, + "counts": { + "INFO": 18, + "SUCCESS": 19 + }, + "blocking": [] + }, + "warnings": [], + "failures": [], + "checks": [ + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received POST request for /mcp (method: initialize)", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.621Z", + "details": { + "method": "POST", + "path": "/mcp", + "body": { + "method": "initialize", + "params": { + "protocolVersion": "2025-11-25", + "capabilities": {}, + "clientInfo": { + "name": "eventrelay-conformance-client", + "version": "1.0.0" + } + }, + "jsonrpc": "2.0", + "id": 0 + }, + "mcpMethod": "initialize" + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 401 response for POST /mcp (method: initialize)", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.624Z", + "details": { + "method": "POST", + "path": "/mcp", + "statusCode": 401, + "mcpMethod": "initialize", + "headers": { + "x-powered-by": "Express", + "www-authenticate": "Bearer error=\"invalid_token\", error_description=\"Missing Authorization header\", resource_metadata=\"http://localhost:40305/.well-known/oauth-protected-resource/mcp\"", + "content-type": "application/json; charset=utf-8", + "content-length": "76", + "etag": "W/\"4c-ptrIdu+3yjAtarglCEu6XVLnz2c\"" + }, + "body": { + "error": "invalid_token", + "error_description": "Missing Authorization header" + } + } + }, + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received GET request for /.well-known/oauth-protected-resource/mcp", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.632Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp" + } + }, + { + "id": "prm-pathbased-requested", + "name": "PRMPathBasedRequested", + "description": "Client requested PRM metadata at path-based location", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.632Z", + "specReferences": [ + { + "id": "RFC-9728", + "url": "https://www.rfc-editor.org/rfc/rfc9728.html#section-3.1" + }, + { + "id": "MCP-2025-06-18-PRM-discovery", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#protected-resource-metadata-discovery-requirements" + } + ], + "details": { + "url": "/.well-known/oauth-protected-resource/mcp", + "path": "/.well-known/oauth-protected-resource/mcp" + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 200 response for GET /.well-known/oauth-protected-resource/mcp", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.633Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "92", + "etag": "W/\"5c-T0K4Flktw8U7A9v/kk6RUFxKhJc\"" + }, + "body": { + "resource": "http://localhost:40305/mcp", + "authorization_servers": [ + "http://localhost:39163" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received GET request for /.well-known/oauth-authorization-server", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.640Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-authorization-server" + } + }, + { + "id": "authorization-server-metadata", + "name": "AuthorizationServerMetadata", + "description": "Client requested authorization server metadata", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.640Z", + "specReferences": [ + { + "id": "RFC-8414-metadata-request", + "url": "https://www.rfc-editor.org/rfc/rfc8414.html#section-3.1" + }, + { + "id": "MCP-Authorization-metadata-discovery", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-server-metadata-discovery" + } + ], + "details": { + "url": "/.well-known/oauth-authorization-server", + "path": "/.well-known/oauth-authorization-server" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 200 response for GET /.well-known/oauth-authorization-server", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.641Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-authorization-server", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "447", + "etag": "W/\"1bf-9foJYAtC4XU7383PRutI9Ti3NLc\"" + }, + "body": { + "issuer": "http://localhost:39163", + "authorization_endpoint": "http://localhost:39163/authorize", + "token_endpoint": "http://localhost:39163/token", + "registration_endpoint": "http://localhost:39163/register", + "response_types_supported": [ + "code" + ], + "grant_types_supported": [ + "authorization_code", + "refresh_token" + ], + "code_challenge_methods_supported": [ + "S256" + ], + "authorization_response_iss_parameter_supported": true, + "token_endpoint_auth_methods_supported": [ + "none" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received POST request for /register", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.646Z", + "details": { + "method": "POST", + "path": "/register", + "body": { + "client_name": "eventrelay-conformance-client", + "redirect_uris": [ + "http://localhost:3000/callback" + ], + "application_type": "native" + } + } + }, + { + "id": "client-registration", + "name": "ClientRegistration", + "description": "Client registered with authorization server", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.646Z", + "specReferences": [ + { + "id": "MCP-Dynamic-client-registration", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/client#dynamic-client-registration" + } + ], + "details": { + "endpoint": "/register", + "clientName": "eventrelay-conformance-client", + "tokenEndpointAuthMethod": "none" + } + }, + { + "id": "sep-837-application-type-present", + "name": "DCR application_type specified", + "description": "Client specified application_type \"native\" during Dynamic Client Registration", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.646Z", + "specReferences": [ + { + "id": "MCP-Dynamic-client-registration", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/client#dynamic-client-registration" + } + ], + "details": { + "application_type": "native" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 201 response for POST /register", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.646Z", + "details": { + "method": "POST", + "path": "/register", + "statusCode": 201, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "174", + "etag": "W/\"ae-DMm4Mje2XxfKtwXyHETj/BCpQxY\"" + }, + "body": { + "client_id": "test-client-1788911526646", + "client_name": "eventrelay-conformance-client", + "redirect_uris": [ + "http://localhost:3000/callback" + ], + "token_endpoint_auth_method": "none" + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received GET request for /authorize", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.653Z", + "details": { + "method": "GET", + "path": "/authorize", + "query": { + "response_type": "code", + "client_id": "test-client-1788911526646", + "code_challenge": "eo92Ht_7xYdv44n-jA57Bh-FVgVTVsvhTsUDgfqKH_o", + "code_challenge_method": "S256", + "redirect_uri": "http://localhost:3000/callback", + "resource": "http://localhost:40305/mcp" + } + } + }, + { + "id": "authorization-request", + "name": "AuthorizationRequest", + "description": "Client made authorization request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.654Z", + "specReferences": [ + { + "id": "OAUTH-2.1-authorization-endpoint", + "url": "https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-authorization-endpoint" + } + ], + "details": { + "query": { + "response_type": "code", + "client_id": "test-client-1788911526646", + "code_challenge": "eo92Ht_7xYdv44n-jA57Bh-FVgVTVsvhTsUDgfqKH_o", + "code_challenge_method": "S256", + "redirect_uri": "http://localhost:3000/callback", + "resource": "http://localhost:40305/mcp" + } + } + }, + { + "id": "pkce-code-challenge-sent", + "name": "PKCE Code Challenge", + "description": "Client sent code_challenge in authorization request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.654Z", + "specReferences": [ + { + "id": "MCP-PKCE-requirement", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection" + } + ] + }, + { + "id": "pkce-s256-method-used", + "name": "PKCE S256 Method", + "description": "Client used S256 code challenge method", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.654Z", + "specReferences": [ + { + "id": "MCP-PKCE-requirement", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection" + } + ], + "details": { + "method": "S256" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 302 response for GET /authorize", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.656Z", + "details": { + "method": "GET", + "path": "/authorize", + "statusCode": 302, + "headers": { + "x-powered-by": "Express", + "location": "http://localhost:3000/callback?code=test-auth-code&iss=http%3A%2F%2Flocalhost%3A39163", + "vary": "Accept", + "content-type": "text/plain; charset=utf-8", + "content-length": "107" + }, + "body": "Found. Redirecting to http://localhost:3000/callback?code=test-auth-code&iss=http%3A%2F%2Flocalhost%3A39163" + } + }, + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received GET request for /.well-known/oauth-protected-resource/mcp", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.659Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp" + } + }, + { + "id": "prm-pathbased-requested", + "name": "PRMPathBasedRequested", + "description": "Client requested PRM metadata at path-based location", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.659Z", + "specReferences": [ + { + "id": "RFC-9728", + "url": "https://www.rfc-editor.org/rfc/rfc9728.html#section-3.1" + }, + { + "id": "MCP-2025-06-18-PRM-discovery", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#protected-resource-metadata-discovery-requirements" + } + ], + "details": { + "url": "/.well-known/oauth-protected-resource/mcp", + "path": "/.well-known/oauth-protected-resource/mcp" + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 200 response for GET /.well-known/oauth-protected-resource/mcp", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.659Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "92", + "etag": "W/\"5c-T0K4Flktw8U7A9v/kk6RUFxKhJc\"" + }, + "body": { + "resource": "http://localhost:40305/mcp", + "authorization_servers": [ + "http://localhost:39163" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received GET request for /.well-known/oauth-authorization-server", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.662Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-authorization-server" + } + }, + { + "id": "authorization-server-metadata", + "name": "AuthorizationServerMetadata", + "description": "Client requested authorization server metadata", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.662Z", + "specReferences": [ + { + "id": "RFC-8414-metadata-request", + "url": "https://www.rfc-editor.org/rfc/rfc8414.html#section-3.1" + }, + { + "id": "MCP-Authorization-metadata-discovery", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-server-metadata-discovery" + } + ], + "details": { + "url": "/.well-known/oauth-authorization-server", + "path": "/.well-known/oauth-authorization-server" + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 200 response for GET /.well-known/oauth-authorization-server", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.662Z", + "details": { + "method": "GET", + "path": "/.well-known/oauth-authorization-server", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "447", + "etag": "W/\"1bf-9foJYAtC4XU7383PRutI9Ti3NLc\"" + }, + "body": { + "issuer": "http://localhost:39163", + "authorization_endpoint": "http://localhost:39163/authorize", + "token_endpoint": "http://localhost:39163/token", + "registration_endpoint": "http://localhost:39163/register", + "response_types_supported": [ + "code" + ], + "grant_types_supported": [ + "authorization_code", + "refresh_token" + ], + "code_challenge_methods_supported": [ + "S256" + ], + "authorization_response_iss_parameter_supported": true, + "token_endpoint_auth_methods_supported": [ + "none" + ] + } + } + }, + { + "id": "incoming-auth-request", + "name": "Incoming-auth-request", + "description": "Received POST request for /token", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.667Z", + "details": { + "method": "POST", + "path": "/token", + "body": { + "grant_type": "authorization_code", + "code": "test-auth-code", + "code_verifier": "3.EgAuYyh9W2s4P11Bi8gYHRUfYY5r4uFnJ~UK-jsSZ", + "redirect_uri": "http://localhost:3000/callback", + "resource": "http://localhost:40305/mcp", + "client_id": "test-client-1788911526646" + } + } + }, + { + "id": "token-request", + "name": "TokenRequest", + "description": "Client requested access token", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.667Z", + "specReferences": [ + { + "id": "OAUTH-2.1-token-request", + "url": "https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-token-request" + } + ], + "details": { + "endpoint": "/token", + "grantType": "authorization_code" + } + }, + { + "id": "pkce-code-verifier-sent", + "name": "PKCE Code Verifier", + "description": "Client sent code_verifier in token request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.667Z", + "specReferences": [ + { + "id": "MCP-PKCE-requirement", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection" + } + ] + }, + { + "id": "pkce-verifier-matches-challenge", + "name": "PKCE Verifier Validation", + "description": "code_verifier correctly matches code_challenge (S256)", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.667Z", + "specReferences": [ + { + "id": "MCP-PKCE-requirement", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection" + } + ], + "details": { + "matches": true, + "storedChallenge": "eo92Ht_7xYdv44n-jA57Bh-FVgVTVsvhTsUDgfqKH_o", + "computedChallenge": "eo92Ht_7xYdv44n-jA57Bh-FVgVTVsvhTsUDgfqKH_o" + } + }, + { + "id": "token-endpoint-auth-method", + "name": "Token endpoint authentication method", + "description": "Client correctly used no authentication (public client) for token endpoint", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.667Z", + "specReferences": [ + { + "id": "OAUTH-2.1-token-request", + "url": "https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-token-request" + } + ], + "details": { + "expectedAuthMethod": "none", + "actualAuthMethod": "none", + "hasAuthorizationHeader": false, + "hasBodyClientSecret": false + } + }, + { + "id": "outgoing-auth-response", + "name": "Outgoing-auth-response", + "description": "Sent 200 response for POST /token", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.668Z", + "details": { + "method": "POST", + "path": "/token", + "statusCode": 200, + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "83", + "etag": "W/\"53-gCpj+9t7XPYdjU7n14YPW/7vfnI\"" + }, + "body": { + "access_token": "test-token-1788911526667", + "token_type": "Bearer", + "expires_in": 3600 + } + } + }, + { + "id": "incoming-request", + "name": "Incoming-request", + "description": "Received POST request for /mcp (method: initialize)", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.672Z", + "details": { + "method": "POST", + "path": "/mcp", + "body": { + "method": "initialize", + "params": { + "protocolVersion": "2025-11-25", + "capabilities": {}, + "clientInfo": { + "name": "eventrelay-conformance-client", + "version": "1.0.0" + } + }, + "jsonrpc": "2.0", + "id": 0 + }, + "mcpMethod": "initialize" + } + }, + { + "id": "valid-bearer-token", + "name": "ValidBearerToken", + "description": "Client provided valid bearer token", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.672Z", + "specReferences": [ + { + "id": "MCP-Access-token-usage", + "url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#access-token-usage" + } + ], + "details": { + "token": "test-token-1788...", + "scopes": [] + } + }, + { + "id": "outgoing-response", + "name": "Outgoing-response", + "description": "Sent 400 response for POST /mcp (method: initialize)", + "status": "INFO", + "timestamp": "2026-09-08T23:52:06.672Z", + "details": { + "method": "POST", + "path": "/mcp", + "statusCode": 400, + "mcpMethod": "initialize", + "headers": { + "x-powered-by": "Express", + "content-type": "application/json; charset=utf-8", + "content-length": "96", + "etag": "W/\"60-+I3pGWaBJPzWK/0N3EGEE0YM2QY\"" + }, + "body": { + "jsonrpc": "2.0", + "id": 0, + "error": { + "code": -32020, + "message": "Missing MCP-Protocol-Version header" + } + } + } + }, + { + "id": "resource-parameter-in-authorization", + "name": "Resource parameter in authorization request", + "description": "Client included resource parameter in authorization request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.695Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + } + ], + "details": { + "resource": "http://localhost:40305/mcp" + } + }, + { + "id": "resource-parameter-in-token", + "name": "Resource parameter in token request", + "description": "Client included resource parameter in token request", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.695Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + } + ], + "details": { + "resource": "http://localhost:40305/mcp" + } + }, + { + "id": "resource-parameter-valid-uri", + "name": "Resource parameter is valid canonical URI", + "description": "Resource parameter is a valid canonical URI (has scheme, no fragment)", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.695Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + } + ], + "details": { + "resource": "http://localhost:40305/mcp" + } + }, + { + "id": "resource-parameter-consistency", + "name": "Resource parameter consistency", + "description": "Resource parameter is consistent between authorization and token requests", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.695Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + } + ], + "details": { + "authorizationResource": "http://localhost:40305/mcp", + "tokenResource": "http://localhost:40305/mcp" + } + }, + { + "id": "resource-parameter-matches-prm", + "name": "Resource parameter matches protected resource metadata", + "description": "Client sent the resource identifier exactly as published in protected resource metadata", + "status": "SUCCESS", + "timestamp": "2026-09-08T23:52:06.695Z", + "specReferences": [ + { + "id": "RFC-8707-Resource-Indicators", + "url": "https://www.rfc-editor.org/rfc/rfc8707.html" + }, + { + "id": "MCP-Resource-Parameter-Implementation", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation" + }, + { + "id": "MCP-Canonical-Server-URI", + "url": "https://modelcontextprotocol.io/specification/draft/basic/authorization#canonical-server-uri" + }, + { + "id": "RFC-9728-resource-identity", + "url": "https://www.rfc-editor.org/rfc/rfc9728.html#section-3.3" + } + ], + "details": { + "prmResource": "http://localhost:40305/mcp", + "authorizationResource": "http://localhost:40305/mcp", + "tokenResource": "http://localhost:40305/mcp" + } + } + ], + "stdout": "", + "stderr": "Starting scenario: auth/token-endpoint-auth-none\nExecuting client: node /home/runner/work/EventRelay/EventRelay/tests/testing/official_mcp_auth_client.mjs http://localhost:40305/mcp\n(node:11049) [DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated.\n(Use `node --trace-deprecation ...` to show where the warning was created)\n\nClient exited with code 1\n\nStderr:\nError: Streamable HTTP error: Error POSTing to endpoint: {\"jsonrpc\":\"2.0\",\"id\":0,\"error\":{\"code\":-32020,\"message\":\"Missing MCP-Protocol-Version header\"}}\n at StreamableHTTPClientTransport.send (file:///home/runner/work/EventRelay/EventRelay/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js:365:23)\n at process.processTicksAndRejections (node:internal/process/task_queues:104:5)\n\nResults saved to /tmp/mcp-conformance-client-iigu4mya/auth/token-endpoint-auth-none-2026-09-08T23-52-06-368Z\nChecks:\n\u001b[90m2026-09-08T23:52:06.621Z\u001b[0m [incoming-request ] \u001b[36mINFO \u001b[0m Received POST request for /mcp (method: initialize)\n\u001b[90m2026-09-08T23:52:06.624Z\u001b[0m [outgoing-response ] \u001b[36mINFO \u001b[0m Sent 401 response for POST /mcp (method: initialize)\n\n\u001b[90m2026-09-08T23:52:06.632Z\u001b[0m [incoming-request ] \u001b[36mINFO \u001b[0m Received GET request for /.well-known/oauth-protected-resource/mcp\n\u001b[90m2026-09-08T23:52:06.632Z\u001b[0m [prm-pathbased-requested ] \u001b[32mSUCCESS\u001b[0m Client requested PRM metadata at path-based location\n\u001b[90m2026-09-08T23:52:06.633Z\u001b[0m [outgoing-response ] \u001b[36mINFO \u001b[0m Sent 200 response for GET /.well-known/oauth-protected-resource/mcp\n\n\u001b[90m2026-09-08T23:52:06.640Z\u001b[0m [incoming-auth-request ] \u001b[36mINFO \u001b[0m Received GET request for /.well-known/oauth-authorization-server\n\u001b[90m2026-09-08T23:52:06.640Z\u001b[0m [authorization-server-metadata ] \u001b[32mSUCCESS\u001b[0m Client requested authorization server metadata\n\u001b[90m2026-09-08T23:52:06.641Z\u001b[0m [outgoing-auth-response ] \u001b[36mINFO \u001b[0m Sent 200 response for GET /.well-known/oauth-authorization-server\n\n\u001b[90m2026-09-08T23:52:06.646Z\u001b[0m [incoming-auth-request ] \u001b[36mINFO \u001b[0m Received POST request for /register\n\u001b[90m2026-09-08T23:52:06.646Z\u001b[0m [client-registration ] \u001b[32mSUCCESS\u001b[0m Client registered with authorization server\n\u001b[90m2026-09-08T23:52:06.646Z\u001b[0m [sep-837-application-type-present ] \u001b[32mSUCCESS\u001b[0m Client specified application_type \"native\" during Dynamic Client Registration\n\u001b[90m2026-09-08T23:52:06.646Z\u001b[0m [outgoing-auth-response ] \u001b[36mINFO \u001b[0m Sent 201 response for POST /register\n\n\u001b[90m2026-09-08T23:52:06.653Z\u001b[0m [incoming-auth-request ] \u001b[36mINFO \u001b[0m Received GET request for /authorize\n\u001b[90m2026-09-08T23:52:06.654Z\u001b[0m [authorization-request ] \u001b[32mSUCCESS\u001b[0m Client made authorization request\n\u001b[90m2026-09-08T23:52:06.654Z\u001b[0m [pkce-code-challenge-sent ] \u001b[32mSUCCESS\u001b[0m Client sent code_challenge in authorization request\n\u001b[90m2026-09-08T23:52:06.654Z\u001b[0m [pkce-s256-method-used ] \u001b[32mSUCCESS\u001b[0m Client used S256 code challenge method\n\u001b[90m2026-09-08T23:52:06.656Z\u001b[0m [outgoing-auth-response ] \u001b[36mINFO \u001b[0m Sent 302 response for GET /authorize\n\n\u001b[90m2026-09-08T23:52:06.659Z\u001b[0m [incoming-request ] \u001b[36mINFO \u001b[0m Received GET request for /.well-known/oauth-protected-resource/mcp\n\u001b[90m2026-09-08T23:52:06.659Z\u001b[0m [prm-pathbased-requested ] \u001b[32mSUCCESS\u001b[0m Client requested PRM metadata at path-based location\n\u001b[90m2026-09-08T23:52:06.659Z\u001b[0m [outgoing-response ] \u001b[36mINFO \u001b[0m Sent 200 response for GET /.well-known/oauth-protected-resource/mcp\n\n\u001b[90m2026-09-08T23:52:06.662Z\u001b[0m [incoming-auth-request ] \u001b[36mINFO \u001b[0m Received GET request for /.well-known/oauth-authorization-server\n\n...[truncated]" + } + ] +} diff --git a/tests/testing/official_mcp_auth_client.mjs b/tests/testing/official_mcp_auth_client.mjs new file mode 100644 index 000000000..0f042404e --- /dev/null +++ b/tests/testing/official_mcp_auth_client.mjs @@ -0,0 +1,216 @@ +#!/usr/bin/env node + +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; +import { + auth, + extractWWWAuthenticateParams, + UnauthorizedError, +} from '@modelcontextprotocol/sdk/client/auth.js'; +import { checkResourceAllowed } from '@modelcontextprotocol/sdk/shared/auth-utils.js'; + +const SCENARIOS = new Set([ + 'auth/metadata-var2', + 'auth/token-endpoint-auth-basic', + 'auth/token-endpoint-auth-post', + 'auth/token-endpoint-auth-none', +]); + +const CIMD_CLIENT_METADATA_URL = + 'https://conformance-test.local/client-metadata.json'; + +class ConformanceOAuthProvider { + constructor(redirectUrl, clientMetadata, clientMetadataUrl) { + this._redirectUrl = redirectUrl; + this._clientMetadata = clientMetadata; + this._clientMetadataUrl = clientMetadataUrl; + } + + get redirectUrl() { + return this._redirectUrl; + } + + get clientMetadata() { + return this._clientMetadata; + } + + get clientMetadataUrl() { + return this._clientMetadataUrl; + } + + clientInformation() { + return this._clientInformation; + } + + saveClientInformation(clientInformation) { + this._clientInformation = clientInformation; + } + + tokens() { + return this._tokens; + } + + saveTokens(tokens) { + this._tokens = tokens; + } + + async redirectToAuthorization(authorizationUrl) { + const response = await fetch(authorizationUrl.toString(), { + redirect: 'manual', + }); + const location = response.headers.get('location'); + if (!location) { + throw new Error(`No redirect location received from ${authorizationUrl}`); + } + const redirectUrl = new URL(location); + const code = redirectUrl.searchParams.get('code'); + if (!code) { + throw new Error('No authorization code in redirect URL'); + } + this._authCode = code; + } + + async getAuthCode() { + if (!this._authCode) { + throw new Error('No authorization code available'); + } + return this._authCode; + } + + saveCodeVerifier(codeVerifier) { + this._codeVerifier = codeVerifier; + } + + codeVerifier() { + if (!this._codeVerifier) { + throw new Error('No code verifier saved'); + } + return this._codeVerifier; + } + + validateResourceURL(defaultResource, configuredResource) { + if (!configuredResource) { + return undefined; + } + if ( + !checkResourceAllowed({ + requestedResource: defaultResource, + configuredResource, + }) + ) { + throw new Error( + `Protected resource ${configuredResource} does not match expected ${defaultResource} (or origin)` + ); + } + return { href: configuredResource }; + } +} + +function unionScopes(prior, challenged) { + const values = [...(prior?.split(' ') ?? []), ...(challenged?.split(' ') ?? [])] + .map((value) => value.trim()) + .filter(Boolean); + return values.length ? [...new Set(values)].join(' ') : undefined; +} + +async function handle401(response, provider, next, serverUrl) { + const { resourceMetadataUrl, scope: challengedScope } = + extractWWWAuthenticateParams(response); + const prior = (await provider.tokens())?.scope; + const scope = unionScopes(prior, challengedScope); + + let result = await auth(provider, { + serverUrl, + resourceMetadataUrl, + scope, + fetchFn: next, + }); + + if (result === 'REDIRECT') { + const authorizationCode = await provider.getAuthCode(); + result = await auth(provider, { + serverUrl, + resourceMetadataUrl, + scope, + authorizationCode, + fetchFn: next, + }); + } + + if (result !== 'AUTHORIZED') { + throw new UnauthorizedError(`Authentication failed with result: ${result}`); + } +} + +function withOAuthRetry(clientName, baseUrl, clientMetadataUrl) { + const provider = new ConformanceOAuthProvider( + 'http://localhost:3000/callback', + { + client_name: clientName, + redirect_uris: ['http://localhost:3000/callback'], + application_type: 'native', + }, + clientMetadataUrl + ); + + return (next) => { + return async (input, init) => { + const makeRequest = async () => { + const headers = new Headers(init?.headers); + const tokens = await provider.tokens(); + if (tokens?.access_token) { + headers.set('Authorization', ['Bearer', tokens.access_token].join(' ')); + } + return next(input, { ...init, headers }); + }; + + let response = await makeRequest(); + if (response.status === 401 || response.status === 403) { + await handle401(response, provider, next, baseUrl); + response = await makeRequest(); + } + if (response.status === 401 || response.status === 403) { + const url = typeof input === 'string' ? input : input.toString(); + throw new UnauthorizedError(`Authentication failed for ${url}`); + } + return response; + }; + }; +} + +async function runAuthClient(serverUrl) { + const client = new Client( + { name: 'eventrelay-conformance-client', version: '1.0.0' }, + { capabilities: {} } + ); + const oauthFetch = withOAuthRetry( + 'eventrelay-conformance-client', + new URL(serverUrl), + CIMD_CLIENT_METADATA_URL + )(fetch); + const transport = new StreamableHTTPClientTransport(new URL(serverUrl), { + fetch: oauthFetch, + }); + await client.connect(transport); + await client.listTools(); + await client.callTool({ name: 'test-tool', arguments: {} }); + await transport.close(); +} + +async function main() { + const scenario = process.env.MCP_CONFORMANCE_SCENARIO; + const serverUrl = process.argv[2]; + + if (!scenario || !serverUrl) { + throw new Error('Usage: MCP_CONFORMANCE_SCENARIO= official_mcp_auth_client.mjs '); + } + if (!SCENARIOS.has(scenario)) { + throw new Error(`Unsupported conformance scenario: ${scenario}`); + } + await runAuthClient(serverUrl); +} + +main().catch((error) => { + console.error(error instanceof Error ? error.stack || error.message : String(error)); + process.exit(1); +}); diff --git a/tests/testing/official_mcp_fixture_server.py b/tests/testing/official_mcp_fixture_server.py new file mode 100644 index 000000000..47cb80f3e --- /dev/null +++ b/tests/testing/official_mcp_fixture_server.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +from http import HTTPStatus +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from typing import Any + +TOOLS = [ + { + "name": "test_simple_text", + "description": "Return a simple text response for conformance testing.", + "inputSchema": {"type": "object", "properties": {}}, + }, + { + "name": "test_error_handling", + "description": "Return a tool error response for conformance testing.", + "inputSchema": {"type": "object", "properties": {}}, + }, +] + +SERVER_INFO = {"name": "eventrelay-conformance-fixture", "version": "1.0.0"} +STATELESS_RESULT_META = { + "resultType": "complete", + "ttlMs": 0, + "cacheScope": "private", +} +ALLOWED_PROTOCOL_VERSIONS = {"2025-11-25", "2026-07-28"} + + +def _jsonrpc_result(request_id: Any, result: dict[str, Any]) -> dict[str, Any]: + return {"jsonrpc": "2.0", "id": request_id, "result": result} + + +def _jsonrpc_error( + request_id: Any, + code: int, + message: str, +) -> dict[str, Any]: + return { + "jsonrpc": "2.0", + "id": request_id, + "error": {"code": code, "message": message}, + } + + +def _stateless_result(request_id: Any, result: dict[str, Any]) -> dict[str, Any]: + return _jsonrpc_result(request_id, {**STATELESS_RESULT_META, **result}) + + +def _normalize_protocol_version(value: Any) -> str: + if isinstance(value, str) and value in ALLOWED_PROTOCOL_VERSIONS: + return value + return "2025-11-25" + + +def _safe_header_part(value: str) -> str: + if "\r" in value or "\n" in value: + raise ValueError("invalid header value") + return value + + +class ConformanceFixtureHandler(BaseHTTPRequestHandler): + protocol_version = "HTTP/1.1" + + def do_POST(self) -> None: # noqa: N802 + if self.path != "/mcp": + self._write_json( + HTTPStatus.NOT_FOUND, + _jsonrpc_error(None, -32601, "Method not found"), + ) + return + + payload = self._read_json() + if payload is None: + self._write_json( + HTTPStatus.BAD_REQUEST, + _jsonrpc_error(None, -32700, "Parse error"), + ) + return + + request_id = payload.get("id") + method = payload.get("method") + params = payload.get("params") or {} + + if request_id is None and str(method).startswith("notifications/"): + self.send_response(HTTPStatus.ACCEPTED) + self.send_header("Content-Length", "0") + self.end_headers() + return + + if method == "initialize": + protocol_version = _normalize_protocol_version(params.get("protocolVersion")) + self._write_json( + HTTPStatus.OK, + _jsonrpc_result( + request_id, + { + "protocolVersion": protocol_version, + "capabilities": {"tools": {}}, + "serverInfo": SERVER_INFO, + }, + ), + headers={"MCP-Protocol-Version": protocol_version}, + ) + return + + if method == "server/discover": + self._write_json( + HTTPStatus.OK, + _stateless_result( + request_id, + { + "supportedVersions": ["2026-07-28"], + "capabilities": {"tools": {}}, + "serverInfo": SERVER_INFO, + }, + ), + headers={"MCP-Protocol-Version": "2026-07-28"}, + ) + return + + if method == "tools/list": + self._write_json( + HTTPStatus.OK, + _stateless_result(request_id, {"tools": TOOLS}), + headers={"MCP-Protocol-Version": "2026-07-28"}, + ) + return + + if method == "tools/call": + tool_name = params.get("name") + if tool_name == "test_simple_text": + self._write_json( + HTTPStatus.OK, + _stateless_result( + request_id, + { + "content": [ + { + "type": "text", + "text": "This is a simple text response for testing.", + } + ] + }, + ), + headers={"MCP-Protocol-Version": "2026-07-28"}, + ) + return + if tool_name == "test_error_handling": + self._write_json( + HTTPStatus.OK, + _stateless_result( + request_id, + { + "isError": True, + "content": [ + { + "type": "text", + "text": "This tool intentionally returns an error for testing", + } + ], + }, + ), + headers={"MCP-Protocol-Version": "2026-07-28"}, + ) + return + + self._write_json( + HTTPStatus.NOT_FOUND, + _jsonrpc_error(request_id, -32601, "Method not found"), + ) + + def do_GET(self) -> None: # noqa: N802 + self._write_json( + HTTPStatus.METHOD_NOT_ALLOWED, + _jsonrpc_error(None, -32000, "Method not allowed."), + ) + + def do_DELETE(self) -> None: # noqa: N802 + self._write_json( + HTTPStatus.METHOD_NOT_ALLOWED, + _jsonrpc_error(None, -32000, "Method not allowed."), + ) + + def log_message(self, _format: str, *_args: Any) -> None: + return + + def _read_json(self) -> dict[str, Any] | None: + try: + length = int(self.headers.get("Content-Length", "0")) + except ValueError: + return None + body = self.rfile.read(length) if length > 0 else b"" + try: + return json.loads(body.decode("utf-8")) if body else {} + except json.JSONDecodeError: + return None + + def _write_json( + self, + status: HTTPStatus, + payload: dict[str, Any], + *, + headers: dict[str, str] | None = None, + ) -> None: + encoded = json.dumps(payload).encode("utf-8") + self.send_response(status) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(encoded))) + for name, value in (headers or {}).items(): + self.send_header(_safe_header_part(name), _safe_header_part(value)) + self.end_headers() + self.wfile.write(encoded) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--port", type=int, default=38765) + args = parser.parse_args() + server = ThreadingHTTPServer(("127.0.0.1", args.port), ConformanceFixtureHandler) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + finally: + server.server_close() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/unit/test_official_mcp_conformance.py b/tests/unit/test_official_mcp_conformance.py new file mode 100644 index 000000000..f80ab5ecf --- /dev/null +++ b/tests/unit/test_official_mcp_conformance.py @@ -0,0 +1,210 @@ +from __future__ import annotations + +import importlib.util +import json +import socket +import subprocess +import sys +import time +from pathlib import Path + +import httpx + + +_ROOT = Path(__file__).resolve().parents[2] +_SCRIPT_PATH = _ROOT / "scripts/testing/official_mcp_conformance.py" +_FIXTURE_SERVER_PATH = _ROOT / "tests/testing/official_mcp_fixture_server.py" + + +def _load_module(): + assert _SCRIPT_PATH.exists(), f"missing harness script: {_SCRIPT_PATH}" + spec = importlib.util.spec_from_file_location("official_mcp_conformance", _SCRIPT_PATH) + module = importlib.util.module_from_spec(spec) + assert spec.loader is not None + spec.loader.exec_module(module) + return module + + +def _free_port() -> int: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.bind(("127.0.0.1", 0)) + return int(sock.getsockname()[1]) + + +def _wait_for_port(port: int, timeout: float = 5.0) -> None: + deadline = time.time() + timeout + while time.time() < deadline: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + if sock.connect_ex(("127.0.0.1", port)) == 0: + return + time.sleep(0.05) + raise AssertionError(f"fixture server did not start on port {port}") + + +def test_required_checks_fail_closed_on_warning() -> None: + module = _load_module() + + summary = module.summarize_checks( + [ + {"id": "tools-list", "status": "SUCCESS"}, + {"id": "tools-list-deterministic-order", "status": "WARNING"}, + ], + required=True, + ) + + assert summary["ok"] is False + assert summary["blocking"] == ["tools-list-deterministic-order:WARNING"] + + +def test_unscored_failures_do_not_block_receipt() -> None: + module = _load_module() + + summary = module.summarize_checks( + [{"id": "tasks-dispatch-and-envelope", "status": "FAILURE"}], + required=False, + ) + + assert summary["ok"] is True + assert summary["counts"]["FAILURE"] == 1 + assert summary["blocking"] == [] + + +def test_fixture_server_keeps_tools_list_order_stable() -> None: + port = _free_port() + proc = subprocess.Popen( + [sys.executable, str(_FIXTURE_SERVER_PATH), "--port", str(port)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + try: + _wait_for_port(port) + headers = { + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + "MCP-Protocol-Version": "2026-07-28", + } + payload = { + "jsonrpc": "2.0", + "method": "tools/list", + "params": { + "_meta": { + "io.modelcontextprotocol/protocolVersion": "2026-07-28", + "io.modelcontextprotocol/clientCapabilities": {}, + "io.modelcontextprotocol/clientInfo": { + "name": "pytest", + "version": "1.0.0", + }, + } + }, + } + + orders = [] + with httpx.Client(timeout=5.0) as client: + for index in range(1, 4): + response = client.post( + f"http://127.0.0.1:{port}/mcp", + headers=headers, + json={**payload, "id": index}, + ) + response.raise_for_status() + body = response.json() + orders.append([tool["name"] for tool in body["result"]["tools"]]) + + assert orders == [orders[0], orders[0], orders[0]] + finally: + proc.terminate() + proc.wait(timeout=5) + + +def test_fixture_server_returns_202_for_initialized_notification() -> None: + port = _free_port() + proc = subprocess.Popen( + [sys.executable, str(_FIXTURE_SERVER_PATH), "--port", str(port)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + try: + _wait_for_port(port) + with httpx.Client(timeout=5.0) as client: + initialize = client.post( + f"http://127.0.0.1:{port}/mcp", + headers={ + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + "MCP-Protocol-Version": "2025-11-25", + }, + json={ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2025-11-25", + "capabilities": {}, + "clientInfo": {"name": "pytest", "version": "1.0.0"}, + }, + }, + ) + initialize.raise_for_status() + notification = client.post( + f"http://127.0.0.1:{port}/mcp", + headers={ + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + "MCP-Protocol-Version": "2025-11-25", + }, + content=json.dumps( + { + "jsonrpc": "2.0", + "id": None, + "method": "notifications/initialized", + "params": {}, + } + ), + ) + + assert notification.status_code == 202 + assert notification.text == "" + finally: + proc.terminate() + proc.wait(timeout=5) + + +def test_fixture_server_does_not_reflect_invalid_protocol_version_header() -> None: + port = _free_port() + proc = subprocess.Popen( + [sys.executable, str(_FIXTURE_SERVER_PATH), "--port", str(port)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + try: + _wait_for_port(port) + with httpx.Client(timeout=5.0) as client: + response = client.post( + f"http://127.0.0.1:{port}/mcp", + headers={ + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + "MCP-Protocol-Version": "2025-11-25", + }, + json={ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2025-11-25\r\nX-Injected: yes", + "capabilities": {}, + "clientInfo": {"name": "pytest", "version": "1.0.0"}, + }, + }, + ) + + assert response.status_code == 200 + assert response.headers["MCP-Protocol-Version"] == "2025-11-25" + assert response.json()["result"]["protocolVersion"] == "2025-11-25" + assert "X-Injected" not in response.headers + finally: + proc.terminate() + proc.wait(timeout=5)