From a5c1e06118b051db2d160bd1c55379aac585559a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 21 Aug 2026 12:49:23 +0000 Subject: [PATCH 1/2] ci: assert oauth refresh metadata Co-authored-by: Akshay Dodeja --- .github/workflows/ci.yml | 17 +++++++--- packages/mcp/WORKOS_MCP_SETUP.md | 54 +++++++++++++++++++++++++++----- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b586d6e3..d2eae32d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -182,7 +182,6 @@ jobs: echo "::notice::Skipping authenticated preview smoke because MCP_EVAL_TOKEN is unavailable" fi - uses: actions/checkout@v4 - if: steps.credential.outputs.available == 'true' - uses: actions/setup-node@v6 if: steps.credential.outputs.available == 'true' with: @@ -193,7 +192,6 @@ jobs: if: steps.credential.outputs.available == 'true' run: npm ci - name: Wait for this commit's Vercel preview - if: steps.credential.outputs.available == 'true' id: vercel env: GH_TOKEN: ${{ github.token }} @@ -224,7 +222,6 @@ jobs: fi echo "inspector-url=${inspector_url}" >> "$GITHUB_OUTPUT" - name: Resolve Vercel preview endpoint - if: steps.credential.outputs.available == 'true' id: preview env: GH_TOKEN: ${{ github.token }} @@ -238,7 +235,20 @@ jobs: echo "Could not resolve a Vercel preview URL for ${VERCEL_INSPECTOR_URL}" exit 1 fi + echo "origin=${preview_url}" >> "$GITHUB_OUTPUT" echo "endpoint=${preview_url}/mcp" >> "$GITHUB_OUTPUT" + - name: Verify authorization server advertises refresh support + if: matrix.protocol-version == '2026-07-28' + env: + PREVIEW_ORIGIN: ${{ steps.preview.outputs.origin }} + run: | + curl --fail --silent --show-error --location --max-time 30 \ + "${PREVIEW_ORIGIN}/.well-known/oauth-authorization-server" \ + | jq --exit-status ' + (.grant_types_supported // [] | index("refresh_token")) != null + and + (.scopes_supported // [] | index("offline_access")) != null + ' > /dev/null - name: POST handshake and tools/list to Vercel preview if: steps.credential.outputs.available == 'true' env: @@ -247,7 +257,6 @@ jobs: MCP_PROTOCOL_VERSION: ${{ matrix.protocol-version }} run: npm run test:http-protocol --workspace @terminal49/mcp - name: Verify preview still belongs to this commit - if: steps.credential.outputs.available == 'true' env: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} diff --git a/packages/mcp/WORKOS_MCP_SETUP.md b/packages/mcp/WORKOS_MCP_SETUP.md index b4e4d991..d3cae7f2 100644 --- a/packages/mcp/WORKOS_MCP_SETUP.md +++ b/packages/mcp/WORKOS_MCP_SETUP.md @@ -61,17 +61,27 @@ In the WorkOS environment referenced by `WORKOS_AUTHORIZATION_SERVER_URL`: ## 3. Smoke tests +### Automated metadata smoke + ```sh ISSUER="" -# AS metadata: 200 with registration_endpoint + S256 -curl -s "$ISSUER/.well-known/oauth-authorization-server" \ - | jq '{registration_endpoint, code_challenge_methods_supported}' - -# DCR is open: expect 201 + client_id -curl -s -X POST "$ISSUER/oauth2/register" \ +# AS metadata: 200 with registration_endpoint, S256, and refresh support +curl --fail --silent --show-error --location \ + "$ISSUER/.well-known/oauth-authorization-server" \ + | jq --exit-status ' + .registration_endpoint != null + and (.code_challenge_methods_supported // [] | index("S256")) != null + and (.grant_types_supported // [] | index("refresh_token")) != null + and (.scopes_supported // [] | index("offline_access")) != null + ' > /dev/null + +# DCR is open: expect 201 + client_id. A refresh-capable client must register +# both authorization_code and refresh_token. +curl --fail --silent --show-error -X POST "$ISSUER/oauth2/register" \ -H 'Content-Type: application/json' \ - -d '{"client_name":"smoke","redirect_uris":["https://example.com/cb"],"grant_types":["authorization_code"],"response_types":["code"]}' + -d '{"client_name":"smoke","redirect_uris":["https://example.com/cb"],"grant_types":["authorization_code","refresh_token"],"response_types":["code"],"token_endpoint_auth_method":"none"}' \ + | jq --exit-status '.client_id != null' > /dev/null # PRM: resource + authorization_servers curl -s https://mcp.terminal49.com/.well-known/oauth-protected-resource | jq @@ -85,6 +95,36 @@ curl -si -X POST https://mcp.terminal49.com/mcp \ # aud == https://mcp.terminal49.com ``` +CI follows the preview deployment's authorization-server metadata redirect and +fails if `grant_types_supported` omits `refresh_token` or `scopes_supported` +omits `offline_access`. These capabilities belong in the WorkOS authorization +server metadata, not the Terminal49 Protected Resource Metadata (PRM). + +### Human refresh smoke + +Refresh requires a real user authorization. Do not store a user password in CI. +Run the local [OAuth test client](./OAUTH_TEST_CLIENT.md), which requests +`openid profile email offline_access` and registers both +`authorization_code` and `refresh_token` grant types. + +1. Click **Authorize** and complete the WorkOS sign-in. +2. In the redacted token output, confirm the response contains a + `refresh_token` and the access token `aud` is + `https://mcp.terminal49.com`. +3. Click **Refresh**. The client posts `grant_type=refresh_token`, the current + refresh token, and `resource=https://mcp.terminal49.com` to the discovered + token endpoint using the same registered client. +4. Confirm the refreshed response contains a new `access_token` whose `aud` + is still `https://mcp.terminal49.com`. +5. Click **Tools List** and confirm `tools/list` succeeds with the refreshed + bearer token. +6. If WorkOS returns a replacement `refresh_token`, retain it for the next + refresh. The local test client does this automatically; it keeps the + previous refresh token only when the response omits a replacement. + +The test client redacts tokens and client secrets in its browser output. Do not +copy credentials into CI output, shared shell history, or issue comments. + ## 4. Per-client notes - **ChatGPT (Apps SDK)** and **Claude connectors** connect **server-side** — no From de7aa9017010ffaf35b3a4e16364b31ba696b92b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 21 Aug 2026 12:52:12 +0000 Subject: [PATCH 2/2] fix: check live oauth metadata in preview CI Co-authored-by: Akshay Dodeja --- .github/workflows/ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2eae32d..286b069c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -235,15 +235,14 @@ jobs: echo "Could not resolve a Vercel preview URL for ${VERCEL_INSPECTOR_URL}" exit 1 fi - echo "origin=${preview_url}" >> "$GITHUB_OUTPUT" echo "endpoint=${preview_url}/mcp" >> "$GITHUB_OUTPUT" - name: Verify authorization server advertises refresh support if: matrix.protocol-version == '2026-07-28' env: - PREVIEW_ORIGIN: ${{ steps.preview.outputs.origin }} + AUTHORIZATION_SERVER_METADATA_URL: https://mcp.terminal49.com/.well-known/oauth-authorization-server run: | curl --fail --silent --show-error --location --max-time 30 \ - "${PREVIEW_ORIGIN}/.well-known/oauth-authorization-server" \ + "$AUTHORIZATION_SERVER_METADATA_URL" \ | jq --exit-status ' (.grant_types_supported // [] | index("refresh_token")) != null and