Skip to content

Support OAuth client secrets for MCP servers - #2169

Open
Aaronontheweb wants to merge 8 commits into
netclaw-dev:devfrom
Aaronontheweb:feat/mcp-oauth-client-secret
Open

Aaronontheweb wants to merge 8 commits into
netclaw-dev:devfrom
Aaronontheweb:feat/mcp-oauth-client-secret

Conversation

@Aaronontheweb

@Aaronontheweb Aaronontheweb commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

Outcome

Netclaw supports pre-registered confidential OAuth clients for HTTP and SSE MCP servers.

Operators can supply both values with:

netclaw mcp add --transport http --client-id <id> --client-secret <secret> <name> <url>

The CLI writes the client ID to netclaw.json.
It writes only the encrypted client secret to secrets.json.

The configured identity reaches the MCP SDK for authorization-code exchange and refresh-token redemption.
The configured secret remains authoritative after token updates, secret rotation, and daemon restart.

Netclaw 0.27.0-beta.4 shipped before this PR was ready.
This change targets the next Netclaw 0.27 prerelease.

Closes #2141.

Scope

PR #2167 is merged and shipped in 0.27.0-beta.4.
This PR adds the permanent confidential-client fix.

This PR does not change the anonymous-discovery flow from #2123.

Security

  • The configuration model uses SensitiveString and blocks JSON serialization of the secret.
  • The public schema rejects OAuthClientSecret in netclaw.json.
  • The confidential-client overload requires a non-null client ID and secret.
  • The CLI rejects invalid confidential-client input before any configuration write.
  • The CLI does not print the secret.
  • A profile replacement removes an omitted old secret.
  • A failed secret write restores the prior public configuration.
  • OAuth token records do not copy a profile-owned secret.
  • Dynamic registration credentials keep their current storage behavior.

Verification

The programmable OAuth server requires a matching confidential-client secret.
It verifies both code exchange and refresh after a daemon restart.

The restart case rotates the configured secret.
It proves that the SDK sends the replacement secret and skips dynamic client registration.

  • Full solution suite: 8,651 passed, 21 platform or opt-in tests skipped, and 0 failed.
  • Release build: 0 errors and one existing ASPIRE010 warning.
  • Focused CLI, configuration, credential-store, and SDK flow tests passed.
  • Strict OpenSpec validation passed with all 15 tasks complete.
  • Slopwatch found 0 issues.
  • The file-header check passed.
  • git diff --check passed.

The behavioral eval suite did not run because the required provider settings are absent.

No live GitHub or Google OAuth exchange ran.
The in-process server provides the protocol-boundary proof.

@Aaronontheweb Aaronontheweb left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The direction is sound, but this revision is not ready to merge.

The profile remains the authority for a configured client secret. The SDK receives that identity for code exchange and token refresh.

The token record keeps dynamic credentials but does not copy a profile secret. The restart test proves secret rotation for one client ID.

Two secret-boundary defects remain:

  1. A profile replacement can retain and transmit an omitted old secret.
  2. The public schema approves a secret that CLI inspection hides and the daemon uses.

I reproduced the first defect with the built CLI. I added a confidential profile, then replaced it with a public client at another URL.

mcp get still reported Client secret: configured after the replacement.

CI passed on revision e2573c35. I did not perform a live provider OAuth exchange.

Comment thread src/Netclaw.Cli/Mcp/McpCommand.cs Outdated
Comment thread src/Netclaw.Configuration/Schemas/netclaw-config.v1.schema.json Outdated
@Aaronontheweb
Aaronontheweb force-pushed the feat/mcp-oauth-client-secret branch 2 times, most recently from 491eb01 to 94b929a Compare September 14, 2026 00:32
@Aaronontheweb
Aaronontheweb force-pushed the feat/mcp-oauth-client-secret branch from 41a062c to ae23323 Compare September 14, 2026 11:21
@Aaronontheweb Aaronontheweb added mcp Model context protocol server / client issues. security Security-related changes labels Sep 14, 2026

@Aaronontheweb Aaronontheweb left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not done reviewing but found some show-stopper code smells

var bound = config.GetSection("McpServers")
.Get<Dictionary<string, McpServerEntry>>() ?? [];

Assert.Equal("configured-client-secret", bound["github"].OAuthClientSecret?.Value);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Demonstrates that secret redaction works


/// <summary>OAuth client secret for a pre-registered confidential client.</summary>
[JsonIgnore]
public SensitiveString? OAuthClientSecret { get; set; }

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

LGTM

Comment thread src/Netclaw.Daemon/Mcp/McpOAuthCredentialStore.cs Outdated
@Aaronontheweb
Aaronontheweb force-pushed the feat/mcp-oauth-client-secret branch from c9f4179 to 38f50a5 Compare September 15, 2026 00:05

@Aaronontheweb Aaronontheweb left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

LGTM

positional.Add(args[i]);
}

if (oauthClientSecret is not null && string.IsNullOrWhiteSpace(oauthClientSecret))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

LGTM

@Aaronontheweb
Aaronontheweb enabled auto-merge (squash) September 15, 2026 03:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mcp Model context protocol server / client issues. security Security-related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP OAuth authentication fails with GitHub remote MCP server

1 participant