Skip to content

feat(pi): support pre-registered OAuth client for MCP servers#68

Merged
ricardoraposo merged 2 commits into
mainfrom
ricardo-/-mcp-oauth-preregistered-client
Jul 9, 2026
Merged

feat(pi): support pre-registered OAuth client for MCP servers#68
ricardoraposo merged 2 commits into
mainfrom
ricardo-/-mcp-oauth-preregistered-client

Conversation

@ricardoraposo

@ricardoraposo ricardoraposo commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds support for pre-registered OAuth clients (client_id + client_secret) on MCP server definitions, alongside the existing "bearer" | "oauth" string auth.

Motivation: some MCP servers (e.g. Árvore backoffice) expose a Dynamic Client Registration endpoint that always returns a fixed, third-party-bound client instead of registering a fresh one. For these, the only way to authenticate is a pre-registered client whose credentials are handed out manually. Until now the hub config's auth property only accepted a string, so there was no way to express this.

Changes

  • core
    • MCPConfig.auth now accepts an MCPOAuthConfig object (type, clientId, clientSecret, scope, redirectUri, clientName, clientUri, grantType) in addition to the "bearer" | "oauth" strings.
    • buildPiMcpEntry normalizes object auth down to the "oauth" string so non-pi editors (OpenCode, etc.) stay correct and never receive the credentials object.
  • pi
    • mcp-wiring now emits a resolved oauth block into .pi/mcp.json, resolving ${env:VAR} / ${VAR} refs at generation time — necessary because pi-mcp-adapter does not interpolate env vars inside the oauth block (only inside env). The generated file is gitignored, so the resolved secret never lands in a committed file.
    • Extracted buildOAuthBlock / buildEntry helpers and added unit tests.
  • cli
    • generateEnvExample now scans auth.clientId / auth.clientSecret so their env refs (e.g. BACKOFFICE_MCP_CLIENT_SECRET) show up in the generated .env.example.

Usage

mcp.custom("backoffice", {
  url: "https://backoffice-mcp.arvore.com.br/mcp",
  auth: {
    type: "oauth",
    clientId: "backoffice-mcp-claude",
    clientSecret: "${env:BACKOFFICE_MCP_CLIENT_SECRET}",
    scope: "openid profile email",
  },
}),

Generates in .pi/mcp.json:

"backoffice": {
  "url": "https://backoffice-mcp.arvore.com.br/mcp",
  "auth": "oauth",
  "oauth": {
    "clientId": "backoffice-mcp-claude",
    "clientSecret": "<resolved from env>",
    "scope": "openid profile email"
  }
}

What was tested

  • pnpm lint — clean across core, cli, pi.
  • pnpm test — 28 tests pass, including 6 new tests for buildOAuthBlock / buildEntry (env-ref resolution, optional field passthrough, string-auth passthrough).
  • pnpm build — all packages build.

Version bumps

  • @arvoretech/hub-core 0.26.1 → 0.26.2
  • @arvoretech/hub (cli) 0.25.1 → 0.25.2
  • @arvoretech/hub-pi 1.5.2 → 1.5.3

Notes / follow-up

This unblocks the hub-config side of pre-registered OAuth. For the Árvore backoffice server specifically, the auth server (auth.arvore.com.br) still rejects the pi localhost redirect (http://localhost:19876/callback) with invalid_redirect_uri because the backoffice-mcp-claude client's allowlist only contains the Claude callbacks. Adding the localhost redirect URI (or issuing a pi-specific client) is a separate server-side change and is not part of this PR.

Summary by CodeRabbit

  • New Features

    • MCP OAuth settings now support richer configuration, including optional client and redirect details.
    • Environment variables can now be resolved from OAuth credentials as well as existing MCP settings.
  • Bug Fixes

    • MCP entries now consistently include OAuth details when provided, while keeping simple auth values unchanged.
  • Tests

    • Added coverage for OAuth configuration handling and environment variable resolution.

Adds support for pre-registered OAuth clients (client_id + client_secret)
on MCP server definitions, alongside the existing bearer/oauth string auth.

- core: widen MCPConfig.auth to accept an MCPOAuthConfig object
  (type, clientId, clientSecret, scope, redirectUri, clientName,
  clientUri, grantType); buildPiMcpEntry normalizes object auth to the
  'oauth' string so other editors stay correct.
- pi: mcp-wiring emits a resolved 'oauth' block into .pi/mcp.json,
  resolving ${env:VAR} refs at generation time (pi-mcp-adapter does not
  interpolate env inside the oauth block). Adds unit tests.
- cli: generateEnvExample scans auth.clientId/clientSecret so their
  env refs land in .env.example.

Bumps: core 0.26.1->0.26.2, cli 0.25.1->0.25.2, pi 1.5.2->1.5.3.
@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
rhm-website Ready Ready Preview, Comment Jul 9, 2026 2:53pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ricardoraposo, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 58aafa01-1c06-454b-b615-cc65e8eb5ec0

📥 Commits

Reviewing files that changed from the base of the PR and between 3dc2247 and 6360521.

📒 Files selected for processing (4)
  • packages/cli/src/commands/generate.ts
  • packages/cli/src/core/hub-config.ts
  • packages/pi/src/extensions/mcp-wiring.test.ts
  • packages/pi/src/extensions/mcp-wiring.ts
📝 Walkthrough

Walkthrough

This PR introduces MCPOAuthConfig object support for MCPConfig.auth, updates buildPiMcpEntry to normalize auth into a string form, adds CLI env-var extraction from OAuth credentials, and adds resolveEnvRefs/buildOAuthBlock/buildEntry to Pi MCP wiring to attach resolved OAuth blocks. Package versions bumped for cli, core, and pi.

Changes

MCP OAuth Auth Support

Layer / File(s) Summary
MCPConfig auth type and OAuth interface
packages/core/src/types.ts
Adds exported MCPOAuthConfig interface and broadens MCPConfig.auth to accept it alongside "bearer" | "oauth".
buildPiMcpEntry auth normalization
packages/core/src/prompt-builders.ts
Sets extra.auth from mcp.auth.type when auth is an object, otherwise passes the string through unchanged.
CLI env var extraction from OAuth credentials
packages/cli/src/commands/generate.ts
extractEnvVarsByMcp now also collects env references from mcp.auth.clientId/clientSecret when mcp.auth is an object, in addition to mcp.env.
Pi MCP wiring OAuth block and env ref resolution
packages/pi/src/extensions/mcp-wiring.ts, packages/pi/src/extensions/mcp-wiring.test.ts
Adds resolveEnvRefs, buildOAuthBlock, and buildEntry to build and attach an oauth block with env-resolved credentials; mcpWiring now uses buildEntry instead of buildPiMcpEntry; tests cover env ref resolution and entry formatting.
Package version bumps
packages/cli/package.json, packages/core/package.json, packages/pi/package.json
Version bumped to 0.25.2, 0.26.2, and 1.5.3 respectively.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant mcpWiring
  participant buildEntry
  participant buildPiMcpEntry
  participant buildOAuthBlock
  participant resolveEnvRefs

  mcpWiring->>buildEntry: buildEntry(mcp)
  buildEntry->>buildPiMcpEntry: buildPiMcpEntry(mcp)
  buildEntry->>buildOAuthBlock: buildOAuthBlock(mcp.auth)
  buildOAuthBlock->>resolveEnvRefs: resolveEnvRefs(clientId)
  buildOAuthBlock->>resolveEnvRefs: resolveEnvRefs(clientSecret)
  resolveEnvRefs-->>buildOAuthBlock: resolved value
  buildOAuthBlock-->>buildEntry: oauth block or undefined
  buildEntry-->>mcpWiring: entry with entry.oauth attached
Loading

Possibly related PRs

  • arvoreeducacao/rhm#48: Both PRs modify packages/pi/src/extensions/mcp-wiring.ts, extending the same MCP config generation flow.
  • arvoreeducacao/rhm#52: Extends the same Pi MCP auth plumbing by updating buildPiMcpEntry/MCPConfig and mcp-wiring.ts with OAuth handling.
  • arvoreeducacao/rhm#67: Both PRs modify buildPiMcpEntry in packages/core/src/prompt-builders.ts.

Suggested reviewers: VitorPiovezan, rafaelandrade

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding support for pre-registered OAuth clients for MCP servers in the pi workflow.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ricardo-/-mcp-oauth-preregistered-client

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/pi/src/extensions/mcp-wiring.test.ts (1)

59-93: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider adding a test for missing env var behavior

The tests cover env ref resolution well, but don't test what happens when an env var is unset (the silent empty-string → dropped field chain). Adding a test would document the current behavior and guard against regressions if the behavior is later changed to warn or throw.

🧪 Suggested test case
it("drops fields when env var is not set", () => {
  delete process.env.NONEXISTENT_VAR;
  const oauth = buildOAuthBlock({
    type: "oauth",
    clientId: "static-id",
    clientSecret: "${env:NONEXISTENT_VAR}",
  });
  expect(oauth).toEqual({ clientId: "static-id" });
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/pi/src/extensions/mcp-wiring.test.ts` around lines 59 - 93, Add a
test in buildEntry/buildOAuthBlock coverage for the unset env-var path: verify
that when an auth field references "${env:...}" and the environment variable is
missing, the resolved value is omitted rather than silently producing an empty
string. Update the mcp-wiring.test.ts cases alongside the existing buildEntry
tests, and reference buildEntry/buildOAuthBlock behavior directly so the
regression is documented if the handling later changes to warn or throw.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/cli/src/commands/generate.ts`:
- Around line 1173-1182: Broaden MCP env var extraction in generate.ts so
.env.example includes auth variables referenced as both ${env:VAR} and ${VAR}.
Update the logic around mcp.env, mcp.auth, and the values collection to detect
plain ${VAR} placeholders in mcp.auth.clientId and mcp.auth.clientSecret as well
as the existing env: form, then feed both into the same vars/seenInGroup flow.

In `@packages/pi/src/extensions/mcp-wiring.ts`:
- Around line 15-19: The env reference resolver is silently converting missing
variables to empty strings, which lets buildOAuthBlock produce an oauth entry
without required credentials. Update resolveEnvRefs and the OAuth flow in
mcp-wiring.ts so missing ${env:...} or ${...} values are detected before they
are dropped, and either emit a warning or throw/fail fast when
clientId/clientSecret would be missing. Use the existing resolveEnvRefs and
buildOAuthBlock symbols to keep the check close to where OAuth config is
assembled.

---

Nitpick comments:
In `@packages/pi/src/extensions/mcp-wiring.test.ts`:
- Around line 59-93: Add a test in buildEntry/buildOAuthBlock coverage for the
unset env-var path: verify that when an auth field references "${env:...}" and
the environment variable is missing, the resolved value is omitted rather than
silently producing an empty string. Update the mcp-wiring.test.ts cases
alongside the existing buildEntry tests, and reference
buildEntry/buildOAuthBlock behavior directly so the regression is documented if
the handling later changes to warn or throw.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7946ad58-2c14-4ac4-a571-116a5edfb4fb

📥 Commits

Reviewing files that changed from the base of the PR and between a782581 and 3dc2247.

📒 Files selected for processing (8)
  • packages/cli/package.json
  • packages/cli/src/commands/generate.ts
  • packages/core/package.json
  • packages/core/src/prompt-builders.ts
  • packages/core/src/types.ts
  • packages/pi/package.json
  • packages/pi/src/extensions/mcp-wiring.test.ts
  • packages/pi/src/extensions/mcp-wiring.ts

Comment thread packages/cli/src/commands/generate.ts
Comment thread packages/pi/src/extensions/mcp-wiring.ts Outdated
- pi: resolveEnvRefs now tracks unset env vars; mcpWiring warns when an
  oauth entry references a missing env var instead of silently emitting a
  config without clientId/clientSecret. Adds a regression test.
- cli: extractEnvVarsByMcp matches both ${env:VAR} and ${VAR} forms and
  collects all refs per value, so .env.example is complete.
- cli: add auth (string | MCPOAuthConfig) to the local MCPConfig type.
@ricardoraposo
ricardoraposo merged commit a7b1afa into main Jul 9, 2026
6 checks passed
@ricardoraposo
ricardoraposo deleted the ricardo-/-mcp-oauth-preregistered-client branch July 9, 2026 14:57
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.

1 participant