ContextForge v1.0.0-RC-3
Release Candidate 3 — Auth Hardening, Plugin Multi-Tenancy, Rust Runtime & Multi-Arch
Final pre-1.0 candidate consolidating 242 commits and 294 closed issues.
Highlights
- 🔐 Auth & RBAC — Token-teams narrowing across Layer 2 RBAC, session-token scope support, OAuth claim validation, JWKS verification for virtual-server MCP, SSO provider fixes (ADFS, Ollama OIDC, email_verified-optional), SSE/message endpoint auth hardening, service-account support.
- 🧩 Plugins — Multi-tenancy with per-tool plugin config, condition evaluation rewritten to hybrid AND/OR, new plugins (output length guard, retry with exponential backoff, Granite Guardian, Rust url_reputation), PII filter hardening, Rust pre-invoke hooks.
- 🦀 Rust — Experimental Rust MCP runtime and session core, pluggable rate-limiter algorithms backed by Rust, top-level Cargo workspace, enforced Rust in build process.
- 🌐 Multi-arch — s390x and ppc64le wheel builders and Containerfile support; native GitHub runners for s390x/ppc64le Docker builds; protobuf segfault resolution on s390x.
- 🖥️ Admin UI — Overflow (three-dot) menu standardization, admin.js module split, role-based visibility gating, table filter persistence across HTMX pagination, 40+ UI/UX fixes.
- 📊 Observability — Langfuse LLM observability via OTEL, W3C Baggage propagation, MCP root/client spans, separate-session writeback pattern for traces, top-performers accuracy fixes.
- 🗄️ Backends — MySQL/MariaDB/MongoDB support removed; PostgreSQL and SQLite only.
⚠️ Breaking Changes — Action Required
See the Breaking Changes section below for migration steps on:
- Plugin condition evaluation now uses hybrid AND/OR logic (was pure OR). Audit with
python scripts/validate_plugin_conditions.py. MAX_MEMBERS_PER_TEAMis no longer baked into team rows. Setmax_members: nullon existing teams to pick up the env-var default.- MySQL / MariaDB / MongoDB backends removed. Migrate to PostgreSQL (
postgresql+psycopg://) or SQLite (dev only).
Upgrade
- Container:
docker pull ghcr.io/ibm/mcp-context-forge:v1.0.0-RC-3 - Helm:
helm install contextforge ./mcp-stack-1.0.0-RC-3.tgz(attached)
Alembic migrations apply automatically on startup. For compose upgrades from prior releases, see docs/docs/manage/upgrade.md.
Artifacts
mcp-stack-1.0.0-RC-3.tgz— packaged Helm chartmcpgateway.sbom.xml— CycloneDX SBOM for the Python distribution
Full Changelog
Overview
Release Candidate 3 is the final pre-1.0 candidate and consolidates 242 commits across the stack. It focuses on auth and RBAC hardening, plugin framework multi-tenancy, the introduction of an experimental Rust MCP runtime, multi-architecture container support (s390x, ppc64le), and continued Admin UI polish:
- 🔐 Auth & RBAC - Token-teams narrowing across Layer 2 RBAC, session-token scope support, OAuth claim validation, JWKS verification for virtual-server MCP, SSO provider fixes (ADFS, Ollama OIDC, email_verified-optional), SSE/message endpoint auth hardening, service-account support.
- 🧩 Plugins - Multi-tenancy with per-tool plugin config, condition evaluation rewritten to hybrid AND/OR, new plugins (output length guard, retry with exponential backoff, Granite Guardian, Rust url_reputation), PII filter hardening, Rust pre-invoke hooks.
- 🦀 Rust - Experimental Rust MCP runtime and session core, pluggable rate-limiter algorithms backed by Rust, top-level Cargo workspace, enforced Rust in build process.
- 🌐 Multi-arch - s390x and ppc64le wheel builders and Containerfile support; native GitHub runners for s390x/ppc64le Docker builds; protobuf segfault resolution on s390x.
- 🖥️ Admin UI - Overflow (three-dot) menu standardization, admin.js module split, role-based visibility gating, table filter persistence across HTMX pagination, 40+ UI/UX fixes.
- 📊 Observability - Langfuse LLM observability via OTEL, W3C Baggage propagation, MCP root/client spans, separate-session writeback pattern for traces, top-performers accuracy fixes.
- 🗄️ Backends - MySQL/MariaDB/MongoDB support removed; PostgreSQL and SQLite only.
⚠️ Breaking Changes
🔌 Plugin Condition Evaluation: Hybrid AND/OR Logic (#3930, #4078)
Action Required: Plugin condition evaluation has changed from pure OR logic to hybrid AND/OR logic.
Previous Behavior (OR Logic): ANY field match in ANY condition triggered plugin execution.
New Behavior (Hybrid AND/OR Logic):
- Within a condition object: ALL fields must match (AND logic)
- Across condition objects: ANY object can match (OR logic)
Impact: Plugins with multiple fields in a single condition object will have different execution behavior. Security policies become more precise and restrictive by default. Enables defense-in-depth with multiple required conditions.
Migration Steps:
- Audit configuration:
python scripts/validate_plugin_conditions.py - Redesign conditions — AND desired → keep fields in same object; OR desired → split into separate condition objects.
- Test thoroughly with
LOG_LEVEL=DEBUG.
# OLD (implicit OR): executed if tenant=healthcare OR tool=patient_reader
conditions:
- tenant_ids: ["healthcare"]
tools: ["patient_reader"]
# NEW Option 1 — AND (no YAML change): executes ONLY if tenant=healthcare AND tool=patient_reader
conditions:
- tenant_ids: ["healthcare"]
tools: ["patient_reader"]
# NEW Option 2 — OR (split into separate objects)
conditions:
- tenant_ids: ["healthcare"]
- tools: ["patient_reader"]Resources:
- Migration Guide:
docs/docs/architecture/MIGRATION-PLUGIN-CONDITIONS.md - Validation Script:
scripts/validate_plugin_conditions.py - Architecture Docs:
docs/docs/architecture/plugins.md#plugin-condition-evaluation
Rollback: Keep configuration backups. Restore previous plugins/config.yaml if issues arise.
👥 MAX_MEMBERS_PER_TEAM No Longer Baked Into Team Rows (#3682, #3588)
Action Required: New teams now store NULL for max_members and resolve the limit at check time from the MAX_MEMBERS_PER_TEAM environment variable. Existing teams created before this change still have the old default baked into the DB and will not automatically pick up env var changes.
To apply the new behavior to existing teams, set each team's max_members to null via the API:
curl -X PUT "http://localhost:8080/teams/<team_id>" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"max_members": null}'Teams with an explicit non-null max_members value will continue to use that value, ignoring the global setting.
🗄️ MySQL/MariaDB/MongoDB Support Removed (#3684, #1688)
Action Required: MySQL, MariaDB, and MongoDB database backends are no longer supported.
| Backend | Status |
|---|---|
| PostgreSQL | Supported — production |
| SQLite | Supported — development only |
| MySQL / MariaDB | Removed |
| MongoDB | Removed |
- The
pymysqlandmariadboptional dependency groups have been removed frompyproject.toml - The default
db_driveris nowpostgresql+psycopg(wasmariadb+mariadbconnector) - MySQL/MariaDB-specific code has been removed from
db.py,bootstrap_db.py,alembic/env.py, and all Alembic migrations docker-compose.mariadb.ymlhas been deleted; MariaDB/MySQL/MongoDB/PHPMyAdmin/mongo-express services removed from debug and performance compose files- Attempting to use an unsupported database backend now raises
ValueErrorat startup
Migration: Switch to PostgreSQL for production. Update
DATABASE_URLto apostgresql+psycopg://connection string. SQLite (sqlite:///./mcp.db) remains available for local development and testing.
Added
🔐 Auth, SSO & RBAC
- OAuth access token verification via JWKS for virtual-server MCP endpoints (#3715)
- OAuth token claim validation before forwarding to MCP servers (#3941)
- Session-token team-narrowing enforced in Layer 2 RBAC permission checks (#3919)
- Team-scope support for session tokens (#3217)
- Token time-restriction validation (#3888)
- ADFS SSO authorization provider (#3798)
- Generic OIDC groups-claim extraction (from
id_tokenwhere available) (#3597, #3719) - Service-account support and pre-invoke security hardening (#3714)
- Role-based Admin UI visibility gating (#3479)
tools.executepermission added to team-scopedviewerrole (#3882, #3881)- Decrypt bearer token before sending to a2a agents (#4019)
🧩 Plugins
- Plugin framework multi-tenancy with per-tool plugin config (#4068)
- Automatic tool discovery with hot/cold classification (#3839)
- Output-length guard plugin (#3841)
- Retry-with-exponential-backoff plugin (#3774)
- Granite Guardian content-moderation plugin and configurable plugin config (#3301)
- Rust
url_reputationplugin (#3728) - Encoded-exfiltration detection plugin: tests, hardening, documentation (#3906)
- In-tree plugins migrated to PyPI (
cpex-*) packages (#3965) PLUGINS_CAN_OVERRIDE_AUTH_HEADERSfeature flag for WXO auth (#3663)
🦀 Rust
- Experimental Rust MCP runtime and session core (#3617)
- Rust MCP runtime pre-invoke plugin hooks and WXO auth support (#3705)
- Pluggable rate-limiter algorithms with Rust-backed execution engine, benchmarks, and validation (#3809)
- Rust plugins restructured as independent crates (#3147)
- ADR-0041 Top-Level Rust Workspace (#3289), ADR-0042 Enforce Rust in build process (#3294)
🌐 Multi-arch & Infra
- s390x wheel-builder Containerfile and workflow (#3797)
- Node.js support on s390x and ppc64le in
Containerfile.lite(#4075) - Native GitHub runners for s390x and ppc64le Docker builds (#3775)
- Configurable platform support for multi-arch container builds (#3507, #3506)
- Helm chart lint and OCI publish workflow (#3454)
📊 Observability
- Langfuse LLM observability integration via OTEL (#3900)
- OpenTelemetry W3C Baggage support for distributed tracing (#4008)
- OTEL root and client spans for MCP flows (#3872)
- Interactive browsable architecture explorer in docs (#4064)
🔌 API
- Auto-populate REST tool schemas from OpenAPI specs (#3167)
- Gateway-ID filtering on prompts and resources listing endpoints (#3676)
- Configurable tool-description forbidden patterns;
auth_valueencoding fix (#3263) - Content-size limits for resources and prompts (US-1) (#3251, #538)
- MIME-type restrictions for resources (US-2) (#3847)
🖥️ Admin UI
- Overflow (three-dot) menu standardized across Gateways, Tools, Servers, Resources, Prompts, Agents, Roots; shared
Admin.overflowMenu()factory (#3519, #4060) admin.jssplit into modules (#3137)- Roots added to global search (#3169)
- MCP tool refresh button (#3802)
- Admin table filters persisted across HTMX pagination and partial refresh (#3647)
🧪 Testing
- End-to-end wrapper test workflow for
mcpgateway(#3612) - OWASP A01 direct and ZAP DAST test suites (#3219)
- MCP protocol load test, performance tuning guide, and profiling docs (#3553)
- CONC-01 gateway parallel-create manual test runner (#3299)
- CONC-02 gateway read-during-write manual runner (#3403)
Changed
- Performance: defer DB bootstrap and lazy-load admin / Rust proxy / llmchat (#4132)
- Performance: set
TCP_NODELAYon sockets for stateful MCP server parity with REST RPS (#4007) - Observability writeback uses separate, independent DB sessions (not atomic with main request; see
CLAUDE.mdObservability Transaction Behavior) (#4050, #3883) - Makefile: parameterize target families; add deprecation framework (#3353)
- Makefile: standardize virtual-environment execution; replace bare
uvwith$(UV_BIN)(#4118, #4123) - Helm chart
values.yamlhardened with secure production defaults (#3550)
Removed
- MySQL, MariaDB, and MongoDB backends (see Breaking Changes above) (#3684)
- Unused
PaginationParams,ObservabilityQueryParams,PerformanceHistoryParamsschemas (#3706, #3708) linting-security-trufflehogmake target (replaced by gitleaks + detect-secrets) (#3874)flake8,darglint,dlint— replaced by ruff D417 (#3933)- Unmaintained
rustls-pemfiledependency from MCP runtime (#3887) - Legacy hidden agents table (eliminated Playwright modal test flake) (#3370)
Fixed
🔐 Security & Auth
- Enforce
token_teamsnarrowing across all Layer 2 RBAC paths (#3932) - Validate Server ID in Streamable HTTP to prevent unauthorized access (#3892)
- Close auth bypass on
/mcp/{server_id}virtual-server endpoints (#3812) - Harden auth for SSE and message endpoints (#3796)
- SSO team-membership stale-revocation (#3856)
- SSO login blocked for providers that omit
email_verified(#3635) - Eliminate duplicate DB sessions in auth and RBAC middleware (#3886)
- Set
teams=Noneinstead of[]for All-Teams API tokens (#3686) - Harden legacy OAuth state handling and document token-audience gap (#3228)
- Logging hardening and sanitization (#3604)
- Dependency security-hardening updates (#3474)
- Update cryptography package and pin transitive dependency (#4102)
- Bump dependency pins; resolve grpc/protobuf version conflict (#3718)
- Replace unsafe shell-invocation with
shutil.rmtree()inrun_mutmut.py(#3944) - Observability
service.versionnow derived dynamically from__version__(no more hardcoded version inobservability.py)
🧩 Plugins
- Honor
fail_on_plugin_errorduring plugin init (#4034) - Tighten PII-filter behavior and Rust masking strategy (#3803)
- Comprehensive Rust PII-filter hardening and regression tests (#3840)
- Rate-limiter: shared state, eviction, thread safety, config validation (#3750)
- Rate-limiter returns proper HTTP status codes and headers (#2668)
- Remove duplicate disabled badge and tag/hook truncation on plugin cards (#3885)
- Add
TOOLS_MANAGE_PLUGINSpermission constant (#4081)
🔌 MCP & API
- Separate query params from body payload in REST tool POST requests (#3720)
- Apply query and header mappings on tool invocation (#3369, #1405)
- Propagate
titlefield for tools, resources, and prompts (#3182) - Prioritize name-based prompt lookup per MCP spec (#3651, #1704)
- Convert
PromptArgumenttypes inprompts/listMCP handler (#3953) - Restore MCP handshake for public tokens (#3636)
- Forwarded RPC non-2xx responses no longer masked as success (#3371, #3365)
- Accept parameterized MIME types in resource validation (#3273)
- Annotate import-endpoint params as
Body()and validateimport_data(#3166) - Move JSONPath modifier to query parameter with improved error handling (#3159)
- Accept camelCase fields on gateway creation (#3685)
- Enforce visibility literals across entity schemas (#3701)
- Update Ollama default endpoints to use native API (#3265)
- Direct proxy paths use shared passthrough header utility (#3677)
🧵 Sessions, Transport & Reliability
- Resolve MCP session-pool memory leak, dead-worker locks, polling inefficiency (#4029)
- Session-pool resource exhaustion (#3952)
- Session pool: isolate cancel scopes via background task ownership (#3739)
- Restore multi-instance HA leader election (#3949)
- MCP Plugin session reconnection (#3639)
- SSE resource subscribe endpoint yields SSE-formatted strings (not raw dicts) (#3595)
- Forward passthrough headers in SSE/WebSocket loopback calls (#3675)
- Consolidate loopback RPC URLs and TLS verification into shared helper (#3696, #3543)
- SSL context cache for mTLS + rotation and HTTP bypass (#3758)
- Disable IPv6 listener in nginx config (#3320)
- Eliminate retry latency in load test for accurate RPS measurement (#4101)
- Restore runtime metric writeback and cover
mcp_runtimein CI (#4124) - Resolve
psycopgimport error on s390x (#3804, #3805) - Resolve s390x container build and protobuf segfault (#3700, #3699)
📊 Observability & Metrics
- Top-performers data loss, dead guards, display bugs, response-time unit conversion (#3794)
- Duplicate DB session in observability middleware (#3600)
- Metrics returning 0 after cleanup; extend
include_metricssupport (#3649) - Resolve Alpine.js
MutationObserverrace condition in observability sub-views (#3967)
🖥️ Admin UI
- Hide deactivated entities in admin UI catalog and API (#3462)
- Remove duplicate on-click causing double-click on CA cert upload (#4090)
- Fix MCP Tool Refresh button (#4083)
- Token revoke button sends DELETE with empty token ID (#4047, #4046)
- "Select All" respects search filters for tools/resources/prompts (#3968)
- Preserve tag filters across page navigation on Virtual Servers (#3717)
- Preserve search filters across pagination (#3492)
- Preserve pagination page after edit modal save (#3389)
- Reset scroll position on tab navigation (#3921)
- Permission-based menu hiding (#3566)
- Clean z-index structure (#3698)
- Show toast notification when user deletion returns an error (#3205)
- Reinit Alpine.js on OOB-swapped pagination controls (#3206)
- Model selection dropdown appearance and interaction (#3806)
- LLM Chat message animation (#3656)
- Redirect authenticated users from login page to dashboard (#3461)
- Redirect to login page on manual
/admin/logout(#3564) - Resolve JS syntax error in pagination and login redirect loop (#3645)
- Populate issuer field when editing OAuth gateway (#3756)
- Display OAuth 2.0 support and configuration in Server Administration UI (#3573)
- Show federated prompt arguments in Admin UI (#3602)
- Clear stale test results when reopening tool/prompt/gateway test modals (#3633)
- JSON validation with 422 error for tool form fields (#3477)
- Exclude display name from required-field validation in tool form (#3464)
- Virtual servers select-all count (#3849)
- Padding for input/select/textarea consistency (#3697)
- Admin-token visibility filter (#3693)
- "+N more" badges clickable in server details modal (#3511)
- Remove non-functional Show/Hide toggles (#3508)
- Team-members modal shows only non-members (#3610)
- Resource test modal buttons no longer refresh page (#3614)
- Infinite
/partialrequest loop on search input (#3863) - Default
include_inactiveto true for servers and gateways (#3404) - Include public MCP objects in team-scoped server associations (#3514)
🗄️ Teams / Bootstrap
MAX_MEMBERS_PER_TEAMset in team forms (#3650)- Pass
max_membersfrom admin UI team create/edit forms (#3487) - Fix team-join RBAC permissions (#3981)
- Team-join validation and error handling (#3623)
- Fix
MultipleResultsFoundinget_user_role_assignment(#3661, #3505) - Synchronize
is_adminflag whenplatform_adminrole is assigned during bootstrap (#3608) - Backfill
admin.overviewandservers.usepermissions to viewer roles (#3390) - Rename orphaned resources to resolve team/name assignment conflict during bootstrap (#3987)
🧰 Misc
- Preserve per-resource visibility on gateway refresh (#3678)
- Preserve server
team_idduring admin UI edits (#3780) _prepare_gateway_for_readno longer mutates ORM object (#3570)- Naive vs aware datetime comparison crashes on SQLite (#3562)
- Restore transaction control to
get_db()for middleware sessions (#3813, #3731) - Align Bedrock
GatewayProviderconfig keys with DB schema (#3732) - LLM chat: mark tool/parsing errors as recoverable in streaming (#3733)
- Cascade A2A agent state changes to associated MCP tools (#3173)
- A2A test endpoint 500 for admin users (#3725)
- Preserve OAuth auth-code guard on
set_gateway_statestale cleanup (#3792) - Helm chart: ingress template YAML rendering and disable TLS for minikube (#3556)
- Helm chart:
TRANSPORT_TYPEvalidation and enable testing in minikube overlay (#3552) - Tool-description forbidden-pattern check added to
ToolUpdate.validate_description(#3785) - Remove semicolon from tool-description forbidden patterns (#3916)
- MCP tool validation fix (#3749)
- Handle
ResourceNotFoundErrorwhen resource is not found (#3628) - Fix
orjson.JSONDecodeErrorin prompt editing endpoints with proper validation (#3582)
Security
See the Fixed → Security & Auth subsection above for enumerated security fixes. Highlights:
- Layer 2 RBAC now enforces session-token team narrowing (#3919, #3932)
- Server ID validation in Streamable HTTP (#3892)
- SSE / message /
/mcp/{server_id}auth hardening (#3796, #3812) - OAuth claim validation before MCP forwarding; legacy state handling hardened (#3941, #3228)
- PII filter hardened (Python + Rust) (#3803, #3840)
- Logging sanitization (#3604)
- Cryptography package and transitive pins updated (#4102)