Skip to content

feat(gateway): release the forest gateway r1 - #1993

Open
Tonours wants to merge 13 commits into
mainfrom
feat/gateway-r1
Open

Tonours wants to merge 13 commits into
mainfrom
feat/gateway-r1

Conversation

@Tonours

@Tonours Tonours commented Oct 12, 2026

Copy link
Copy Markdown
Member

Merges the Forest Gateway R1 train into main: addGateway() on the agent, the standalone @forestadmin/gateway package (forest-gateway), service account credentials on the Gateway MCP, and the plan refusal surfaced by agent-bff.

fixes PRD-1333
fixes PRD-1334
fixes PRD-1337
fixes PRD-1353
fixes PRD-1354
fixes PRD-1372
fixes PRD-1374

What

Every change below was reviewed and approved in its own PR against feat/gateway-r1:

PR Package Change
#1964 agent addGateway() as the single gateway entry point
#1967 agent addGateway() served through the gateway switch
#1974 agent-bff gateway allowlist applied to API OAuth
#1979 agent-bff customer-facing naming becomes Gateway API
#1978 gateway (new) MCP and API on one port, ghcr image published on release tag
#1944 forestadmin-client, mcp-server service account credential (fgw_ / fbff_) accepted on the MCP
#1985 agent-bff sends ?service=api on token exchanges, plan refusal becomes 403 plan_feature_missing

main was merged into the train beforehand (18dac204b, no conflict). The conflicts of #1944 and #1985 with the train were resolved as unions on their branches.

Why

  • Squash-only repo: this PR lands as one commit, so multi-semantic-release bumps every touched package once (feat, minor) and publishes @forestadmin/gateway for the first time.
  • Server side: ForestAdmin/forestadmin-server#8575 (plan gates, flags off by default). Merging this PR first is safe, ?service=api is ignored by servers that do not validate the query.
  • Docs docs: document the embedded Forest Gateway docs#53 waits for the npm and ghcr publications, then the hosted API switch (ForestAdmin/forestadmin-server#8581).

How to test

CI of each PR on the train. Locally after the train sync, on the #1944 and #1985 merge results:

Package Result
forestadmin-client 394/394
mcp-server 1576/1576, tsc clean
agent-bff 2265/2265, tsc clean
gateway 196/196, tsc clean

Real-stack proof of the service account on the MCP: trust bench gateway-mcp-sa (merge of #1978 + #1944 against #8575): PASS, baseline without #1944 answers 401.

Definition of Done

General

  • Write an explicit title for the Pull Request, following Conventional Commits specification
  • Test manually the implemented changes
  • Validate the code quality (indentation, syntax, style, simplicity, readability)

Security

  • Consider the security impact of the changes made

https://claude.ai/code/session_0127TaBC6gYXxGAzzFRRfSt9

Tonours and others added 9 commits October 5, 2026 17:26
* feat(gateway): serve the MCP and the API on one port

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: lint and test the gateway package

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(gateway): build against the workspace agent-bff and mcp-server

Exact pins fall behind the workspace on each release, since a private
package is skipped by multi-semantic-release, and Yarn then pulls the
published versions instead of linking the local ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(mcp-server): export the listener variables parseMcpEnv reads

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(gateway): follow the listener variable names agent-bff and mcp-server own

A rename on their side now fails the gateway's typecheck, or carries
over through the exported name, instead of silently moving the API's
public url and port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(agent-bff): export the openapi --output handling for other CLIs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(gateway): reuse agent-bff's openapi --output handling

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(gateway): read one set of env vars, legacy names as aliases (#1980)

* feat(gateway): warn that forest-mcp-server and forest-bff are deprecated (#1990)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(mcp-server): accept a service account credential

An fgw_ or fbff_ bearer is resolved on the Forest server with service mcp,
cached, and turned into a short-lived agent token. The resolve client lives
in forestadmin-client so mcp-server imports nothing from agent-bff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(mcp-server): validate tags and log why a credential resolve failed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(forestadmin-client): keep why a gateway key resolve was unreachable

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(mcp-server): sign the service account agent token in the shared claims shape

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: sim <sim@local>
…#1985)

* feat(agent-bff): declare the api service and surface the plan refusal

* fix(agent-bff): log the plan refusal as expected, with its reason

* refactor(agent-bff): extract the session access failure mapping

---------

Co-authored-by: sim <sim@local>
@linear-code

linear-code Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

@qltysh

qltysh Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

13 new issues

Tool Category Rule Count
qlty Structure Function with many parameters (count = 8): buildAgentMiddlewares 3
qlty Structure Function with many returns (count = 7): dispatchCli 3
qlty Duplication Found 47 lines of similar code in 2 locations (mass = 226) 3
qlty Structure Function with high complexity (count = 14): start 2
qlty Structure High total complexity (count = 65) 1
qlty Structure Complex binary expression 1

@qltysh

qltysh Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Qlty


Coverage Impact

⬆️ Merging this pull request will increase total coverage on main by 0.1%.

Modified Files with Diff Coverage (42)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
packages/agent-bff/src/oauth/forest-server-client.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/openapi/openapi-document.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/oauth/oauth-routes.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/http/bff-http-error.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent/src/embedded-bff.ts96.0%57
Coverage rating: A Coverage rating: A
packages/agent-bff/src/cli-dispatch.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/config/missing-config-warning.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent/src/agent.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/oauth/session-lifecycle.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/cors/cors-middleware.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/api-key/api-key-error.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/oauth/oauth-exchange-error.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/auth/forest-server-token-middleware.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/oauth/oauth-error.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/build-bff.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/config/env-config.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent/src/bff-routes.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/index.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/cors/per-key-origin.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent/src/framework-mounter.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/openapi/render-openapi.ts100.0%
New file Coverage rating: A
packages/agent-bff/src/cli-output.ts100.0%
Coverage rating: A Coverage rating: A
packages/forestadmin-client/src/index.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent/src/utils/express-to-koa.ts100.0%
New file Coverage rating: A
packages/gateway/src/env-aliases.ts100.0%
New file Coverage rating: A
packages/gateway/src/version.ts100.0%
New file Coverage rating: A
packages/gateway/src/cli-dispatch.ts100.0%
New file Coverage rating: A
packages/gateway/src/index.ts100.0%
New file Coverage rating: A
...stadmin-client/src/oauth-client-allowlist/is-client-allowed.ts100.0%
New file Coverage rating: A
packages/gateway/src/standalone-handler.ts100.0%
New file Coverage rating: A
packages/gateway/src/healthcheck.ts100.0%
New file Coverage rating: A
packages/gateway/src/gateway-env.ts100.0%
New file Coverage rating: A
...min-client/src/oauth-client-allowlist/normalize-domain-list.ts50.0%33
New file Coverage rating: A
packages/gateway/src/run-gateway.ts100.0%
New file Coverage rating: A
packages/agent/src/gateway.ts100.0%
New file Coverage rating: A
packages/forestadmin-client/src/gateway-api-key/index.ts100.0%
New file Coverage rating: A
packages/forestadmin-client/src/gateway-api-key/resolve-error.ts100.0%
Coverage rating: A Coverage rating: A
packages/mcp-server/src/index.ts100.0%
Coverage rating: A Coverage rating: A
packages/mcp-server/src/mcp-env.ts100.0%
Coverage rating: A Coverage rating: A
packages/mcp-server/src/forest-oauth-provider.ts100.0%
Coverage rating: A Coverage rating: A
packages/mcp-server/src/server.ts100.0%
New file Coverage rating: A
packages/mcp-server/src/gateway-api-key-authenticator.ts100.0%
Total99.7%
🤖 Increase coverage with AI coding...
In the `feat/gateway-r1` branch, add test coverage for this new code:

- `packages/agent/src/embedded-bff.ts` -- Line 57
- `packages/forestadmin-client/src/oauth-client-allowlist/normalize-domain-list.ts` -- Line 33

🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

@nbouliol nbouliol left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spec (PRD-1333, PRD-1334, PRD-1337, PRD-1353, PRD-1354, PRD-1372, PRD-1374): conforms, except the MCP discovery documents not answering 503 while the embedded Gateway starts, inline on gateway.ts:30.

Applies to: the PR as a whole
Claude Opus 5.5 (claude-opus-5-5): Should fix: a reader of addGateway cannot learn why the MCP and the API share one /oauth/* route, and no ADR records it.

Code

Customers register clients against <basePath>/oauth/*?service=api, the code never says why <basePath>/api/oauth/token was turned down, and docs/adr holds no Gateway record.

A way out: run /adr on the shared OAuth route and on addGateway superseding mountAiMcpServer, addBff and forest-bff.

Reproduction
  1. Read agent.ts and gateway.ts for why OAuth is shared across services.
  2. Search docs/adr for the Gateway, which returns no record.

@@ -1,9 +1,12 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { GatewayServices } from './gateway';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Opus 5.5 (claude-opus-5-5): This pull request lands in files that keep accumulating debt, so refactor them, or open a ticket naming each file, before the next feature.

Files
  • packages/agent/src/agent.ts: 894 lines, 12 commits in 90 days, mixes lifecycle, datasource customization and embedded MCP, BFF and Gateway composition.
  • Split first: lines 366-625 and 739-793, the embedded-service composition, into its own module.
  • packages/agent-bff/src/build-bff.ts: 653 lines, 22 commits in 90 days, mixes body guards, the OAuth edge, the API-key edge and agent routes.
  • Split first: lines 305-552, the agent edge, into its own module.
  • packages/agent-bff/src/openapi/openapi-document.ts: 705 lines, 18 commits in 90 days, mixes the error-response catalog, the route table and document assembly.
  • Split first: lines 36-268, the error-response catalog, into its own module.
  • packages/agent-bff/src/openapi/unfolded-paths.ts: 786 lines, 11 commits in 90 days, mixes filter-schema derivation, request bodies and operation registration.
  • Split first: lines 106-265, the filter-schema derivation, into its own module.

@Tonours Tonours Oct 12, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of scope for the R1 release, which only merges already reviewed changes. Ticketed as PRD-1542, one sub-issue and one PR per file: PRD-1543 (agent.ts embedded-service composition), PRD-1544 (build-bff.ts agent edge), PRD-1545 (openapi-document.ts error catalog), PRD-1546 (unfolded-paths.ts filter-schema derivation). Each is a pure move, starting once this PR is merged.

Comment thread packages/agent/src/agent.ts
Comment thread packages/agent/src/gateway.ts Outdated
export { default as GatewayApiKeyResolveError } from './resolve-error';
export type { GatewayApiKeyResolveErrorParams } from './resolve-error';

const API_KEY_PATTERN = /^(?:fgw|fbff)_([0-9a-f]{16})_([0-9a-f]{64})$/;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Opus 5.5 (claude-opus-5-5): Should fix: the next change to the key format or the resolve contract must land twice, or the API and the MCP accept different credentials.

Code

This file copies the key pattern, RESOLVE_PATH and the error mapping that agent-bff/src/api-key/api-key.ts:3 and api-key-client.ts still own.

A way out: keep forestadmin-client as the single home and have agent-bff's api-key-authenticator.ts consume GatewayApiKeyClient with service: 'api'.
Also moves: agent-bff's tests of api-key-client.ts and api-key.ts, and the hashApiKey copy in gateway-api-key-authenticator.ts:76.

Reproduction
  1. Add a key prefix to API_KEY_PATTERN in this file only.
  2. Send a key with that prefix to the Gateway MCP, which resolves it.
  3. Send the same key to the Gateway API, which answers 401.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and already ticketed: PRD-1409 moves agent-bff's resolve onto GatewayApiKeyClient (single key pattern, resolve path and error mapping, public agent-bff exports kept as re-exports). It was left out of #1944 on purpose (decided 2026-09-30) to keep this release scoped; not fixed in this PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reopened: the two copies still stand at this head, and PRD-1409 tracks their merge outside this pull request.

How it was checked

Predicate: a key format or resolve contract defined in both forestadmin-client and agent-bff/src/api-key.
API_KEY_PATTERN still sits in both gateway-api-key/index.ts:6 and agent-bff/src/api-key/api-key.ts:3, untouched since the review.
PRD-1409 covers exactly this move, so the thread stays open until it lands or a reviewer accepts the deferral.

Comment thread packages/agent/test/agent-gateway-wiring.test.ts Outdated
Comment thread packages/agent-bff/test/openapi/openapi-document.test.ts Outdated
@Tonours

Tonours commented Oct 12, 2026

Copy link
Copy Markdown
Member Author

Review follow-up for the PR-level finding (no ADR for the shared OAuth route): recorded in c0f3658, docs/adr/2026-10-12-one-gateway-serves-the-mcp-and-the-api-behind-a-shared-oauth-route.md. It covers addGateway superseding mountAiMcpServer / addBff / forest-bff as deprecated aliases, why MCP and API share <basePath>/oauth/* told apart by ?service=api, and why <basePath>/api/oauth/* and registry-based routing were turned down.

Comment thread packages/agent/src/gateway.ts Outdated
Comment thread packages/agent/src/gateway.ts
@nbouliol

Copy link
Copy Markdown
Member

Closed: docs/adr/2026-10-12-one-gateway-serves-the-mcp-and-the-api-behind-a-shared-oauth-route.md records the shared OAuth route.

How it was checked

Predicate: a reader who cannot learn why the MCP and the API share <basePath>/oauth/*.
The ADR names the ?service=api dispatch, the turned-down <basePath>/api/oauth/* and registry routing, and the deprecated aliases.
No test can pin a recorded decision, so this close stays unpinned.

@nbouliol nbouliol left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved at 28c1702: the remaining API-key duplication finding is deferred to PRD-1409.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants