[1.1.0] - 2026-10-19 - Modern MCP Protocol, New Redesigned Web UI, Federated Completions, and Catalog Pagination
Overview
Release 1.1.0 delivers modern MCP protocol negotiation on by default, federated completions, MCP catalog pagination, MCP Origin/Host transport security enforcement, federated gateway tool-name collision detection, and configuration import ownership enforcement. It also removes deprecated anyio monkey-patch settings and the in-tree mcp-servers/ directory. This release consolidates 69 PRs across:
- Protocol & Transport -
MCP_CLIENT_CONNECT_MODEandMCP_INBOUND_PROTOCOL_MODEnow default toauto, inbound MCP era negotiation is complete,completion/completeis forwarded to the owning upstream, downstream MCP catalog lists are paginated, and federated resource names carry the gateway name as a prefix. - Security & Auth - Added
private_key_jwttoken endpoint client authentication, MCP Origin/Host enforcement, configuration import ownership enforcement, call-time URL scheme allowlisting, DCR outbound request pinning, and external IdP token fallback on all auth paths. - API & Platform - Added named A2A response models, the invitee invitations API, passwordless SSO user provisioning, per-user MCP server rate limiting, and success and error counts in the metrics timeseries.
- Operations - Added
make container-bump-image-versions, moved CSS linting from Stylelint to Biome, migrated Tailwind CSS to v4, removed the anyio monkey-patch settings, the Rust MCP config surface, and the in-treemcp-servers/directory. - Testing - Eliminated pytest warnings and redundant tests, restored OAuth gateway membership coverage, verified resource template federation, and fixed the live-gateway E2E suite.
Added
Protocol & Transport
-
Modern MCP protocol negotiation is on by default (#7061) -
MCP_CLIENT_CONNECT_MODEandMCP_INBOUND_PROTOCOL_MODEnow default toautoinstead oflegacy. Outbound upstream connections probeserver/discoverand negotiate the 2026-07-28 revision, with transparent fallback to the legacyinitializehandshake. Inbound clients may sendmcp-protocol-version: 2026-07-28instead of receiving a 400. Set either variable tolegacyto restore the previous behaviour. -
Inbound MCP era negotiation (#7156) - Completed inbound MCP era negotiation.
-
Federated completions (#6629, #6760) - Forward
completion/completeto the owning upstream with request metadata. Both legacy and modern MCP connections remain supported through existing negotiation settings; forwarding has no modern-only gate. -
Streamable HTTP
serverInfostamping (#6566, #7145) - The streamable HTTP server is configured with the app name and version forserverInfo. -
Federated resource names prefixed with gateway names (#6780) - Federated resource names carry the owning gateway's name as a prefix.
-
structuredContentaccepts any JSON value for 2026-era callers (#7244) - Tool results on the 2026-07-28 wire now accept any JSON value (string, number, array, null, or object) asstructuredContent. The 2025 wire is unchanged: non-object values are stripped and the text copy incontentis used instead. -
Federated gateway tool-name collisions (#6534) - Gateway registration, refresh, OAuth discovery, reactivation, rename, and visibility updates now reject detected tool-name collisions in public, team, and private visibility scopes. Gateway automation must handle the endpoint's conflict response when a previously accepted colliding registration is rejected. Existing duplicate rows require administrator review before affected invocation names are usable. Operators can identify duplicates with:
SELECT name, COUNT(*) AS duplicate_count FROM tools WHERE visibility = 'public' GROUP BY name HAVING COUNT(*) > 1 ORDER BY name; SELECT team_id, name, COUNT(*) AS duplicate_count FROM tools WHERE visibility = 'team' GROUP BY team_id, name HAVING COUNT(*) > 1 ORDER BY team_id, name; SELECT owner_email, name, COUNT(*) AS duplicate_count FROM tools WHERE visibility = 'private' GROUP BY owner_email, name HAVING COUNT(*) > 1 ORDER BY owner_email, name;
API & Platform
- Named response models for A2A routes (#6820, #6699) - Added named response models for
POST /v1/a2a/{agent_name}/invoke,POST /v1/a2a/invoke, andPOST /v1/a2a/{agent_name}/jsonrpc. The OpenAPI schema now exposes typed components while preserving the routes' passthrough behavior. - Invitee invitations API (#6960) - Added an API for invitee invitations.
- Per-user MCP server rate limiting (#7191) - Adopted per-user rate limiting for MCP servers.
- Metrics timeseries counts (#7138) - The observability metrics timeseries returns success and error counts.
Security & Auth
private_key_jwttoken endpoint client authentication (#6991) - OAuth token requests can authenticate with RFC 7523private_key_jwt.- Passwordless SSO user provisioning (#6692, #7180, #7219) - Added a passwordless SSO user provisioning service and its API. #7180 gated the API behind a feature flag. #7219 removes that flag.
- JWT lifecycle clarification after database reset - Local JWTs remain cryptographically valid when database storage is lost but the signing key remains unchanged. Database cleanup is not credential rotation. Destructive resets must rotate
JWT_SECRET_KEYwhen old-token invalidation is required. Persistent database storage and short-lived local tokens remain recommended for production deployments.
Operations & Tooling
- Container image version bumper (#5811) - Added
make container-bump-image-versionsto bump container image version pins. - CSS linting moves from Stylelint to Biome (#7162) -
make lint-weband thelint-webCI job now lintmcpgateway/static/*.csswith Biome, configured inbiome.json. The CI matrix entry is renamed fromstylelinttobiome-css. Stylelint and its configs leave the dev dependencies, which removes thebracesadvisory (GHSA-vfj7-8cjw-p6xm) fromnpm audit. make testing-upweb UI URL (#6415) - The startup summary now shows aContextForge Web UIrow whose port comes from the resolved Compose configuration. The printed URL now matches the port Compose publishes whenWEB_UI_PORTis set only in.env. Resolution failure stops the target instead of printing a fallback URL.- Pinned-resource load test (#6984) - Added a pinned-resource performance benchmark stack and run history to the load test suite.
mcp-servers/directory removed (#7078) - The unsupported sample/test MCP servers, the Go/Python scaffolding templates, and themcp-url-to-markdown-testsworkflow leave the repository. The six Python servers now live in IBM/contextforge-examples undermcp-servers/python/; the scaffolding templates are deleted without migration. The performance compose generator and benchmark docs now use the publishedghcr.io/ibm/cfex-mcp-fast-time-serverandghcr.io/ibm/cfex-mcp-benchmark-serverimages. The/v1/mcp-serversREST API is unchanged: it remains the product-language alias for the gateways API.
ContextForge Web UI
-
New Web UI v0.2.4 (release notes) - Introduces a redesigned, more user-friendly interface for ContextForge, separate from the built-in Admin UI. The Web UI runs as a separate application and targets ContextForge API v1.1.0.
-
Docker Compose startup - From a configured gateway checkout, start the
uiprofile:make compose-pull PROFILE="--profile ui" make compose-up PROFILE="--profile ui"
PROFILEreplaces the Makefile's default profiles. Onlinux/amd64, usePROFILE="--profile with-fast-time --profile ui"in both commands to retain the default fast-time service.Open http://localhost:3001, or the port configured through
WEB_UI_PORT. Compose starts the gateway, Web UI v0.2.4, and dedicated UI Redis session store. The UI connects tohttp://gateway:4444inside the Compose network. -
Separate npm startup - Install Node.js 22.22.1 or later and npm. Configure the gateway's
.envand generate required secrets withmake setupbefore the first run. For an existing.env, usemake init-secrets-patch-envto replace weak or placeholder secrets. Start the configured gateway in one terminal:make dev
In another terminal, clone the UI release and install both frontend and BFF dependencies:
git clone --branch v0.2.4 --depth 1 https://github.com/contextforge-org/contextforge-web-ui.git cd contextforge-web-ui cp .env.example .env npm install npm install --prefix serverSet
CONTEXTFORGE_URL=http://localhost:8000,COOKIE_SECURE=false, andPORT=3000in the UI's.envfor local HTTP. AdjustCONTEXTFORGE_URLto the gateway's actual address and port. LeaveREDIS_URLunset to use the in-process development session store; sessions reset when the BFF restarts.npm run dev:all
Open http://localhost:3000. This command starts the BFF and Vite frontend together. Access the UI through the BFF on port 3000, which proxies frontend requests to Vite on port 5173. See the v0.2.4 setup guide for configuration details.
Breaking Changes
- Downstream MCP catalog pagination (#6990, #7171) — Python MCP lists now return at most
MCP_LIST_PAGE_SIZE=100items by default. Clients that ignorenextCursorreceive only the first page. Database order changes fromcreated_at DESCtoid ASC. Server-scoped lists exclude other users' private rows for unrestricted administrators; administrators retain their own private rows. Multi-page direct-proxy lists now require Redis. ShareAUTH_ENCRYPTION_SECRETacross workers and keep traversals on one version during deployment. Restart traversals after reconnect, rotation, or rollback. Rust tools lists remain complete until the bridge forwards client cursors. This change delivers part of #6990. See pagination configuration. - Completion authorization (#6760) -
POST /protocol/completion/completenow requirestools.readRBAC permission. Previously, authentication alone passed this endpoint's authorization check. Before upgrading from 1.0.11, granttools.readto completion callers' roles and include it in replacement tokens with explicit permission scopes. Denied callers receive HTTP 403 before upstream access. See completion permission migration. - Experimental anyio cancellation monkey-patch removed (#5427) - Removed the opt-in
ANYIO_CANCEL_DELIVERY_PATCH_ENABLEDandANYIO_CANCEL_DELIVERY_MAX_ITERATIONSworkaround frommcpgateway/transports/sse_transport.py, together with theSSE_TASK_GROUP_CLEANUP_TIMEOUTsetting. The gateway now requiresanyio>=4.15.0, which fixes the_deliver_cancellationCPU spin loop upstream (anyio issues #695 and #1111). Unknown environment keys are ignored, so existing deployments start without changes. Remove the retired variables from your configuration to keep it accurate. The matching Helm values and docker-compose entries are also removed. DanglingMCP_SESSION_POOL_CLEANUP_TIMEOUTreferences, which no code path ever read, are removed from docker-compose, the Helm chart, and.env.example. SSE connection protection and cleanup timeouts remain the containment strategy; seedocs/docs/operations/cpu-spin-loop-mitigation.md. - Rust MCP config surface removed (#7255, #6177) - Removed the Rust MCP configuration surface (closes #5819) and the Python Rust-runtime proxy integration (closes #5818), together with the remaining Rust MCP references in the Makefile, docs, and comments.
x-mcp-headerannotation validation on tool registration (#6773) -POST /tools(create) andPUT /tools/{id}(update) now validatex-mcp-headerannotations ininput_schemaagainst the MCP 2026-07-28 constraints. Payloads with invalid annotations are rejected with HTTP 422. Previously these passed validation silently. Update any tool registration payload that uses non-conformingx-mcp-headervalues before upgrading.
Security
- Configuration import ownership enforcement (#7243) -
POST /v1/importwithconflict_strategy=updateno longer overwrites tools, resources, prompts, or gateways that the importer does not own. Bulk registration now runscheck_resource_ownership()before it modifies an existing row and reportsPermission deniedfor rows the caller does not own. Bulk update without a caller identity changes nothing. The import call toupdate_gateway()now passes the importer identity. Theadmin.importroutes useglobal_only=True, so a team-scoped role no longer satisfies them. The Admin UI import now passes the authenticated email asimported_by. Deployments that grantadmin.importthrough a team-scoped role must move that grant to a global role. - MCP Origin/Host enforcement (#6875) - Implements MCP 2025-11-25 §transport-security: a present-but-unlisted
Originheader on/mcpis now rejected with HTTP 403. SetMCP_ALLOWED_ORIGINSto a comma-separated list of allowed origins to enable enforcement (default: empty, backward-compatible). An optional companion settingMCP_ALLOWED_HOSTSenforces exacthost:portmatching on theHostheader. Enforcement runs at the public/mcpmount viaMCPOriginHostGate, covering the Python ingress path. - A2A plugin header trust (#7157) - A2A pre-invoke plugins can supply credentials and custom headers without the agent caller-header allowlist. Plugin output now overrides configured agent authentication, and an empty
Authorizationvalue suppresses all fallback authorization. The gateway retains inbound caller filtering, sanitizes plugin input, resolves output case-insensitively, and stripsX-Vault-Tokensbefore egress. - Outbound request validation in the DCR path (#6961) - Dynamic client registration validates and pins outbound requests.
- Rate limit tier matching for
/v1paths (#6879) - Rate limit tier matching normalizes/v1paths. - URL scheme allowlist at call time (#6881) - The gateway enforces the URL scheme allowlist at call time and scans the database on startup.
- JSON Schema pattern evaluation (#6957) - The gateway bounds JSON Schema pattern evaluation in a worker process.
- Outbound HTTP connection handling (#6965) - Hardened outbound HTTP connection handling. Outbound URL validation runs even when
SSRF_PROTECTION_ENABLED=false. Dangerous-protocol, control-character, embedded-credential, and XSS checks apply unconditionally. A deployment that relied on the old side effect to reach a non-HTTP(S) URL will see it rejected. Pooled upstream MCP sessions no longer follow HTTP redirects. An upstream that answers/ssewith a 307 redirect to/sse/fails instead of following it; register the redirected URL directly. Connection setup fails closed when a caller-supplied HTTP client's TLS context cannot be read. Some outbound requests connect to a single validated address; a dual-stack upstream whose first-sorted address is unreachable can fail. Requests to a validated address use a dedicated connection pool. Behavior is unchanged when an environment proxy (HTTP_PROXY,HTTPS_PROXY,ALL_PROXY) applies to the target. - Candidate credential handshakes (#6877) - Candidate credential handshakes are isolated.
- Virtual-server scoped tool invocation (#6672) - Hardened virtual-server scoped tool invocation.
- Token scoping for plugin-binding and completion routes (#6709) - Token scoping maps plugin-binding routes. Token scoping maps the completion route to
tools.read. - Input validation and output escaping tightening (#7076) - Tightened input validation and output escaping across gateway paths. ADR-056 CSP hardening applies.
- Result body HTML escaping (#7239) -
escapeHtmlis applied to the tool result body before rendering, preventing stored XSS in result output. - DCR outbound request pinning (#7081) - Dynamic client registration pins outbound requests to the validated endpoint address.
Fixed
Auth & OAuth
- Email claim for generic OIDC providers (#6484) - SSO resolves an email claim for generic OIDC providers.
- External-IdP verification in
get_current_user()(#6424, #7049) - External-IdP verification is wired intoget_current_user(), and all auth paths fall back to external IdP tokens. private_key_jwtfollow-ups (#7190) - Restoredprivate_key_jwtfollow-ups dropped from #6991.- OAuth token handling (#7240) - Improved OAuth token handling.
- OAuth gateway catalog on empty fetch (#6049) - The gateway preserves its catalog when an OAuth fetch returns empty.
Gateway, Catalog & Tools
- Frontend email routes (#6953) - Invitation and password emails include the React
/appmount prefix.UI_BASE_URLaccepts a deployment base or a full frontend root ending in/app. Full roots produce a startup warning and have only the final mount segment removed during normalization. For a deployment prefix/contextforge/app, set the full roothttps://ui.example.com/contextforge/app/appto preserve both segments in emailed links. The invitation frontend route remains tracked by #6776. - Modern MCP clients no longer see the deprecated logging capability (#6630, #6631, #7030) -
server/discoverresponses for2026-07-28clients now omitcapabilities.logging. MCP 2026-07-28 deprecates that capability (SEP-2577), and ContextForge sends no request-scoped log messages. Clients on2025-11-25and older still receive the capability and can calllogging/setLevel. Use OpenTelemetry and structured logging for gateway observability. Modern clients reach the gateway only whenMCP_INBOUND_PROTOCOL_MODE=auto;docker-compose.ymlnow passes that variable to the gateway, with alegacydefault. - Session-affinity worker ID (#6688) - The gateway computes the session-affinity worker ID from the live PID after fork.
- Refresh throttle anchor (#7130) - The refresh throttle anchors to
cycle_started_at, so the gateway no longer skips refreshes. - Gateway URL rejection messages (#7096) - Gateway URL rejections name the cause. Fixed CA signing status.
- Oversize
_meta(#7259) - The gateway rejects oversize_metawith-32602and keeps the upstream resource error. psutil.cpu_freq()on macOS (#7087) - Guardedpsutil.cpu_freq()againstSystemError.- Tool error translation (#7169) - Upstream JSON-RPC error codes are preserved in tool results. The error code is reported under
_meta["io.contextforge/errorCode"]and the result retains theisErrorshape. Previously the code was lost and the SDK made up a transport-level code. Clients that rely on a specific error code from the gateway's own fallback will see the upstream code instead. - Prompt error translation (#7204) - Prompt calls that fail with missing required arguments return
-32602(INVALID_PARAMS). Upstream JSON-RPC errors are relayed with their original code and sanitizeddata. Previously the shape and code varied across failure paths. - Error response format standardization (#7079) - JSON-RPC error response bodies are now formatted consistently across all gateway paths using a shared
ErrorFormatter. Clients that parsed the rawdetailormessagefield of error responses may need to update their parsers.
Observability & A2A
- W3C trace context in A2A invocations (#7093) - A2A invocations propagate W3C trace context.
- Tenantless background spans (#7155) - OpenTelemetry suppresses tenantless background spans.
Admin UI
- Admin UI edit modals (#7271) - Table rows stay in the
innerHTMLguard. Edit modals use delegated handlers.
Build, CI & Dependencies
- OS package refresh before Docker security scans (#7186) - CI refreshes OS packages before Docker security scans.
- Checkov skip placement - Moved the checkov skip off the nginx
FROMline.
Changed
- Duplicate
Gateway.last_seenannotation removed (#6682) - Removed the duplicate model annotation.
Documentation
- Container image version bumping (#7137) - Replaced the manual UBI tag steps with
make container-bump-image-versions. - Deprecation lifecycle (#6996) - Adopted the MCP-style deprecation lifecycle and recorded the deprecation backlog.
Testing
- Pytest warnings and redundant tests (#6895) - Eliminated pytest warnings, dead test code, and redundant auth tests.
- Resource template federation (#6962) - Verified resource template federation through the gateway.
- OAuth gateway membership (#7210) - Restored OAuth gateway membership regression coverage.
- Pre-provisioned SSO (#7183) - Validated pre-provisioned SSO authentication and documented onboarding.
- Live-gateway E2E suite - Fixed suite ordering, error shapes, and cleanup.
Chores
| PR | Description |
|---|---|
| #7132 | routine dependencies update - 2026-10-05 |
| #7135 | migrate Tailwind CSS v3 to v4 and fix npm CVEs |
| #6769 | validate the Code Engine registry secret and clarify env logging |
| #7213 | rate limit middleware updates |
| #7250 | replace mcp-server-filesystem examples with mcp-server-fetch
|
| #7257 | routine dependency update |
| #7267 | docker-compose.yml dual-era test configuration
|
| #7199 | follow-up to #6877 |
| #7201 | follow-up to #7157; ADR-057 |
| #7200 | follow-up to #6996 |
Release preparation bumped every version reference to 1.1.0, refreshed npm dependencies (12 packages updated, 0 vulnerabilities), bumped the NODEJS_IMAGE pin to 10.2-1792386062, updated uv.lock and Cargo.lock, normalised .bumpversion.cfg indentation, and removed Rust MCP leftovers from the Makefile, docs, and comments.