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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions docs/mcp/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ Connects to one server, runs `initialize` + `tools/list`, and prints
the discovered tools with truncated input schemas. Exits non-zero on
any failure (useful in CI).

For an `oauth` server this reads the token stored by `forge mcp login`,
honoring the same `mcp.token_store_path` (forge.yaml) / `MCP_TOKEN_STORE_PATH`
(env) override as `login`/`logout` — so a server logged in under a custom
store path resolves correctly. Run `forge mcp login <name>` first;
`test` does not perform interactive login.

Flags:
- `--call <tool>` — also invoke this tool after listing.
- `--args '<json>'` — JSON arguments for `--call`. Default `{}`.
Expand All @@ -46,22 +52,36 @@ forge mcp test linear --call list_issues --args '{"first":5}'

Runs the OAuth 2.1 PKCE flow against the named server. Opens a
`127.0.0.1` loopback listener on a random port, opens the operator's
browser at the configured `authorize_url`, exchanges the returned
code for tokens, and persists them encrypted at
browser at the authorization endpoint, exchanges the returned code for
tokens, and persists them encrypted at
`~/.forge/credentials/mcp_<name>.json`.

**Discovery (#316):** when the server's `auth` block omits
`client_id` / `authorize_url` / `token_url`, login **discovers** them
from the server `url` (RFC 9728 → RFC 8414) and **dynamically registers**
a client (RFC 7591). The discovered endpoints + minted `client_id` are
persisted alongside the token as `mcp_reg_<name>.json` and reused on
refresh — login never re-registers. Explicit config in `forge.yaml`
overrides discovery. See
[configuration.md](configuration.md#oauth-discovery--dynamic-client-registration-316).

Requires `auth.type: oauth` in the server's config. Fails fast for
bearer / static / no-auth servers.
bearer / static / no-auth servers, and fails closed with a clear message
if a server advertises no metadata / no registration endpoint and no
`client_id` is configured.

For Kubernetes deployments:

```sh
# On your laptop:
forge mcp login linear

# Bundle the credentials file into a Secret:
# Bundle the credentials into a Secret. For a discovery-based server,
# include the registration record too (mcp_reg_<name>.json) — the
# runtime refresh path needs the discovered token_url + client_id:
kubectl create secret generic mcp-tokens \
--from-file=mcp_linear.json=$HOME/.forge/credentials/mcp_linear.json
--from-file=mcp_linear.json=$HOME/.forge/credentials/mcp_linear.json \
--from-file=mcp_reg_linear.json=$HOME/.forge/credentials/mcp_reg_linear.json

# In the pod spec:
volumeMounts:
Expand All @@ -75,7 +95,11 @@ env:

## `forge mcp logout <name>`

Deletes the stored OAuth token for the server. Idempotent.
Deletes the stored OAuth token **and** the discovery/registration record
(`mcp_reg_<name>.json`) for the server. Idempotent. Clearing the
registration is the recovery path when an authorization server revokes
the dynamically-registered client: the next `forge mcp login` re-discovers
and re-registers from scratch.

```sh
forge mcp logout linear
Expand Down
6 changes: 6 additions & 0 deletions docs/mcp/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ rows.
| `forge mcp login` opens browser but `state mismatch` after consent | Stale callback or malicious redirect attempt | Re-run; if persistent, check for browser extensions injecting URLs |
| At runtime: `mcp: oauth token revoked` | Refresh token denied — usually expired or user revoked | `forge mcp login <name>` again; re-bundle the Secret |
| At runtime: `mcp: no stored token for "<name>" — run 'forge mcp login ...'` | Token store empty inside the pod | Mount the Secret correctly; set `HOME` so the store path resolves |
| `forge mcp test` reports `no stored token` right after a successful login | `test` read a different store than `login` wrote | Set the same `mcp.token_store_path` (forge.yaml) or `MCP_TOKEN_STORE_PATH` for both — `test` now honors it (fixed) |
| `could not discover an authorization server` at login | Server has no RFC 9728 metadata / wrong endpoint | Use the Streamable HTTP `/mcp` URL (not `/sse`); or set `authorize_url`/`token_url`/`client_id` explicitly |
| `advertises no registration_endpoint and no client_id` at login | Server doesn't support RFC 7591 dynamic registration | Configure `client_id` (+ `authorize_url`/`token_url`) explicitly for that server |
| `issued a confidential client (client_secret)` at login | Server returned a secret; Forge is public-PKCE only | Configure `client_id`/`authorize_url`/`token_url` explicitly (Forge won't store a client secret) |
| At runtime: `mcp: oauth token revoked` after the AS revoked the DCR client | Persisted registration is stale | `forge mcp logout <name>` (clears the registration record) then `forge mcp login <name>` to re-discover + re-register |

## Tool name conflicts

Expand All @@ -37,6 +42,7 @@ rows.
|------------------------------------------------------------------------------|---------------------------------------------------------|----------------------------------------------------------------|
| Locally the agent works; in K8s it fails with `transport unavailable` | NetworkPolicy egress doesn't include the MCP server | Rebuild — `egress_stage.go` merges `MCPDomains` automatically; re-apply manifests |
| OAuth refresh fails with `transport: dial` from the pod | `token_url` host blocked by NetworkPolicy | The build merges this too — verify the latest `egress_allowlist.json` |
| Discovery-based server: refresh blocked because the auth-server host isn't allowlisted | The build-time freeze can't know a *discovered* host | The runtime learns it from the login-time `mcp_reg_<name>.json` and merges it — ensure that file is mounted alongside the token (see [cli-reference](cli-reference.md#forge-mcp-login-name)) |

## Runtime behavior

Expand Down
Loading