Skip to content

docs(changelog): MCP server API token auth for headless and CI use#152

Draft
samgutentag wants to merge 1 commit into
mainfrom
sam-gutentag/changelog-mcp-api-token-auth
Draft

docs(changelog): MCP server API token auth for headless and CI use#152
samgutentag wants to merge 1 commit into
mainfrom
sam-gutentag/changelog-mcp-api-token-auth

Conversation

@samgutentag
Copy link
Copy Markdown
Member

What shipped

The Trunk MCP server now accepts a Trunk organization API token via the standard Authorization: Bearer <token> header as an alternative to OAuth. OAuth stays the default for interactive clients; the API token path covers CI jobs, scripts, and any client without an OAuth flow. The server tries the OAuth (JWT) path first, then falls back to looking the token up as an org API token, authenticating at the org level.

Source

  • Eng PR: trunk-io/trunk2#3381 (merged 2026-04-08)
  • Linear: TRUNK-18215 (no absorbed duplicates)
  • Date basis: eng PR mergedAt (2026-04-08)

Four wired sites

  1. changelog/2026-04-08-flaky-tests-mcp-api-token-auth.mdx — new entry
  2. docs.json — added to the Changelog 2026 group (between 04-13 and 03-27)
  3. changelog/index.mdx — new <Update> under April 2026
  4. flaky-tests/changelog.mdx — new April 2026 section with the same <Update>

Notes

  • Source code consistently uses "API token" (org-level), not "API key". Header verified as Authorization: Bearer <token> against trunk2#3381.
  • Filed under Flaky Tests since the MCP reference docs live under flaky-tests/reference/mcp-reference/. The feature itself is product-neutral (MCP tools span Flaky Tests setup, fix-flaky, framework detection), but Flaky Tests is the right home for now.
  • Docs link: https://docs.trunk.io/flaky-tests/reference/mcp-reference/configuration/bearer-auth

🤖 Generated with Claude Code

Add changelog entry for the Trunk MCP server accepting a Trunk org API
token via the Authorization: Bearer header as an alternative to OAuth.

Source eng PR: trunk-io/trunk2#3381 (merged 2026-04-08)
Linear: TRUNK-18215 (no absorbed duplicates)
Date basis: eng PR mergedAt

Wired into all four sites:
- changelog/2026-04-08-flaky-tests-mcp-api-token-auth.mdx (new entry)
- docs.json (Changelog 2026 group)
- changelog/index.mdx (April 2026)
- flaky-tests/changelog.mdx (new April 2026 section)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mintlify
Copy link
Copy Markdown
Contributor

mintlify Bot commented May 29, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
trunk 🟢 Ready View Preview May 29, 2026, 5:40 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@samgutentag samgutentag added changelog PR touches the changelog (auto-generated drafts, hosting, formatting, indexing). ready to merge Verify docs PR: customers can use this. Ready to publish. labels May 29, 2026
@samgutentag
Copy link
Copy Markdown
Member Author

samgutentag commented May 29, 2026

Verification status (2026-05-29): blocked

Eng PR not merged. Hold.

  • Flag state: LaunchDarkly not consulted (the auth path is ungated; no flag to read).
  • Eng PRs: trunk-io/trunk2#3381 (merged 2026-04-08, intact on main) AND follow-up trunk-io/trunk2#3918 (fix(mcp): return terminal 401 for non-JWT, unknown bearer tokens) which is currently OPEN.
  • Flag: none. The org API-token auth path is unconditional.
  • Signals: the base feature (#3381) is merged and live, but the corrective follow-up #3918 is unmerged. Per the eng-PR rule, any referenced eng PR being unmerged classifies the docs PR as blocked. #3918 changes how the server responds to malformed/unknown bearer tokens, so the documented behavior should not publish until it lands.

Next: hold in draft. Re-run once #3918 merges and rolls out. (Prior verdict awaiting eng label is unchanged.)

@samgutentag samgutentag added the code-verified verify-docs-against-code: all factual claims confirmed in source. label May 29, 2026
@samgutentag
Copy link
Copy Markdown
Member Author

Code verification (2026-05-28): 5 confirmed / 0 contradicted / 0 ambiguous / 0 unverifiable

All claims verified against the merged source in trunk2#3381 (ts/apps/mcp/). Note: the MCP server lives in trunk-io/trunk2 (ts/apps/mcp/), not the trunk-io/trunk backend the skill's repo map defaults to.

Claim Verdict Source
Auth uses the Authorization: Bearer <token> header confirmed with-mcp-authkit.ts:78-85
Token is a Trunk organization API token (not "API key") confirmed with-mcp-authkit.ts:14-20
Server tries JWT (OAuth) first, falls back to API token lookup confirmed with-mcp-authkit.ts:108-119
Authenticates at the org level confirmed with-mcp-authkit.ts:119
Endpoint URL is https://mcp.trunk.io/mcp confirmed README.md

No contradictions. The entry's "API token" naming matches source exactly; the source never uses "API key" for this path.


Source #1 — Authorization: Bearer header (confirmed)

File: trunk-io/trunk2/ts/apps/mcp/src/with-mcp-authkit.ts#L78-L85

// Extract Bearer token from Authorization header
const authorizationHeader = request.header("Authorization");
if (!authorizationHeader) {
  return unauthorized("Missing Authorization Header");
}
const [scheme = "", token] = authorizationHeader.split(" ");
if (!/^Bearer$/i.test(scheme) || !token) {
  return unauthorized("Invalid Authorization Header");
}

Reasoning: The server reads the Authorization header and requires the Bearer scheme (case-insensitive). This is the same header for both OAuth and API token auth, exactly as the entry states.

Source #2 — org API token, not API key (confirmed)

File: trunk-io/trunk2/ts/apps/mcp/src/with-mcp-authkit.ts#L14-L20

// Try to authenticate using an organization API token.
async function tryApiTokenAuth(
  token: string,
): Promise<{ organizationId: string; orgSlug: string } | undefined> {
  const servicesClient = await servicesPrismaClient();
  const org = await servicesClient.organization.findFirst({
    where: { DebuggerApiToken: token },

Reasoning: The token is looked up against the organization table (DebuggerApiToken column) and resolves to an org, not a user. The code and README consistently call this an "API token", never "API key". The entry's naming is correct.

Source #3 — JWT first, then API token fallback (confirmed)

File: trunk-io/trunk2/ts/apps/mcp/src/with-mcp-authkit.ts#L108-L119

// Not a JWT at all (e.g., an API token) — fall through to API token lookup
if (
  error instanceof jose.errors.JOSEError &&
  error.message.includes("Invalid Compact JWS")
) {
  // Step 2: Try API token authentication
  const apiTokenResult = await tryApiTokenAuth(token);
  if (apiTokenResult) {
    return next(request, {
      authMethod: "api-token",

Reasoning: OAuth JWT verification runs first; only when the token fails JWT parsing (Invalid Compact JWS) does the server fall back to the org API token lookup. This matches the entry's "tries the OAuth (JWT) path first; if the token isn't a valid JWT, it looks the token up as an org API token". The resulting context is tagged authMethod: "api-token" at the org level.

Source #4 — endpoint URL (confirmed)

File: trunk-io/trunk2/ts/apps/mcp/README.md

{
  "mcpServers": {
    "trunk": {
      "url": "https://mcp.trunk.io/mcp",
      "headers": { "Authorization": "Bearer <your-trunk-api-token>" }
    }
  }
}

Reasoning: The README's API-token client config block (added in this PR) matches the entry's JSON example verbatim, including the https://mcp.trunk.io/mcp URL and the Bearer header.

@samgutentag samgutentag added awaiting eng Verify docs PR: eng PR not merged. Hold. pending Verify docs PR: eng merged but flag off in prod. Hold off. and removed ready to merge Verify docs PR: customers can use this. Ready to publish. code-verified verify-docs-against-code: all factual claims confirmed in source. pending Verify docs PR: eng merged but flag off in prod. Hold off. labels May 29, 2026
@samgutentag
Copy link
Copy Markdown
Member Author

On hold until the MCP auth follow-up lands: trunk-io/trunk2#3918 (fix(mcp): return terminal 401 for non-JWT, unknown bearer tokens).

The API-token auth path this entry documents is merged and live, but #3918 corrects how the server responds to malformed/unknown bearer tokens. Holding publication of this changelog entry until that fix is in production so the documented behavior matches. Re-run /verify-docs-pr 152 once #3918 merges and rolls out; if green, rebase + regenerate nav and mark ready.

Copy link
Copy Markdown
Member Author

Verification status (May 30, 2026): blocked

Eng PR not merged. Hold.

  • Flag state: LaunchDarkly not consulted (no flag gates this feature; auth path is unconditional).
  • Eng PR: trunk-io/trunk2#3381 (merged 2026-04-08, intact on main). Follow-up trunk-io/trunk2#3918 (fix(mcp): return terminal 401 for non-JWT, unknown bearer tokens): Slack search of #eng, #production-notifications, #staging-notifications, and #merge-notifications for "trunk2#3918" and "mcp 401 bearer tokens" returned no merge confirmation as of this sweep. Still assumed open.
  • Flag: none.
  • Signals: No Slack signal confirms trunk2#3918 has merged. Holding blocked verdict until merge is confirmed.

Keep in draft. Re-run once trunk2#3918 is confirmed merged and the corrected 401 behavior is in production.


Generated by Claude Code

Copy link
Copy Markdown
Member Author

Verification status (May 31, 2026): blocked

The corresponding eng fix is still open; docs cannot merge until the feature ships.

  • Flag state: not consulted
  • Eng PR: trunk2#3918 (OPEN as of May 31, 2026)
  • Flag: none
  • Signals: trunk2#3918 implements the MCP API token 401-fix; merging docs ahead of that would document behavior users can't yet access

Next action: wait for trunk2#3918 to merge, then re-verify and merge.


Generated by Claude Code

Copy link
Copy Markdown
Member Author

samgutentag commented Jun 1, 2026

Verification status (June 1, 2026): blocked

Eng PR not merged. Hold.

  • Flag state: LaunchDarkly not consulted (no flag gates this path; org API-token auth is unconditional).
  • Eng PRs: trunk-io/trunk2#3381 (merged 2026-04-08, base feature live) AND follow-up trunk-io/trunk2#3918 (fix(mcp): return terminal 401 for non-JWT, unknown bearer tokens), still OPEN as of this sweep.
  • Flag: none.
  • Signals: base feature #3381 merged and intact on main; corrective follow-up #3918 remains unmerged. Per the eng-PR rule, any referenced eng PR unmerged classifies the docs PR as blocked, so the documented 401 behavior should not publish yet.

Next: keep in draft. Re-run once trunk2#3918 merges and the corrected behavior is in production. PR is conflicting (shared changelog nav files); conflicts handled separately, not part of this verdict.


Generated by Claude Code

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

Labels

awaiting eng Verify docs PR: eng PR not merged. Hold. changelog PR touches the changelog (auto-generated drafts, hosting, formatting, indexing).

Development

Successfully merging this pull request may close these issues.

1 participant