SecurityReading time: 11 minutes

OAuth 2.1 + PKCE for MCP Servers: How Token Rotation Protects ServiceNow

A walk through the security model that SnowCoder MCP uses to keep AI clients from turning into a soft underbelly for your ServiceNow instance.

The Problem MCP Has to Solve

Model Context Protocol turns any AI client into a privileged operator of remote systems. That is the appeal, and it is also the risk. An MCP server that holds the keys to a ServiceNow instance is exactly the kind of asset attackers like: high-value, multi-tenant, and reachable from anywhere a developer can run an IDE.

The naive answer is "use an API key." That answer fails for three reasons. API keys are long-lived. They are usually scoped too broadly. And they have no built-in story for what to do when one leaks. SnowCoder MCP uses OAuth 2.1 with PKCE (S256), Dynamic Client Registration under RFC 7591, refresh-token rotation, replay-attack detection, and granular scopes. Each of those is doing a job that an API key would not do.

This post breaks the model apart and explains how the pieces interact when a ServiceNow workload is on the other side.

The Handshake, End to End

When Claude Code, Cursor, ChatGPT Desktop, or any other supported MCP client first calls SnowCoder MCP, the following happens. None of it is bespoke; it is standard OAuth 2.1 with PKCE, applied to MCP.

# 1. Client generates a PKCE pair
code_verifier  = random_url_safe(64)
code_challenge = BASE64URL(SHA256(code_verifier))

# 2. Dynamic Client Registration (RFC 7591)
POST https://mcp.snowcoder.ai/register
{
  "client_name": "Cursor on laptop-7a2",
  "redirect_uris": ["http://127.0.0.1:51777/callback"],
  "scope": "mcp:read mcp:write kb:read projects:read"
}
-> 201 Created
{ "client_id": "snc_clt_9af...", "token_endpoint_auth_method": "none" }

# 3. Authorization request
GET https://mcp.snowcoder.ai/authorize
  ?response_type=code
  &client_id=snc_clt_9af...
  &redirect_uri=http://127.0.0.1:51777/callback
  &scope=mcp:read+mcp:write+kb:read+projects:read
  &code_challenge=<code_challenge>
  &code_challenge_method=S256

# 4. User logs in, consents to scopes, gets redirected with code

# 5. Token exchange (note: code_verifier required)
POST https://mcp.snowcoder.ai/token
{
  "grant_type": "authorization_code",
  "code": "...",
  "code_verifier": "<code_verifier>",
  "client_id": "snc_clt_9af..."
}
-> 200 OK
{
  "access_token": "...",     // short-lived
  "refresh_token": "...",    // single use, rotated
  "expires_in": 900
}

The two security properties that matter from this handshake are the PKCE binding (only the original client can redeem the authorization code) and the short access-token lifetime. After 15 minutes the token is dead and the refresh flow takes over.

Refresh-Token Rotation, In Detail

Refresh-token rotation is the part that most teams under-implement. SnowCoder MCP runs strict rotation: each refresh-token use returns a new refresh token and invalidates the previous one. If a refresh token is ever reused, the server treats it as a compromise signal.

POST https://mcp.snowcoder.ai/token
{
  "grant_type": "refresh_token",
  "refresh_token": "rft_72ab...",
  "client_id": "snc_clt_9af..."
}
-> 200 OK
{
  "access_token": "...",       // fresh, 15 min
  "refresh_token": "rft_91cd..." // NEW refresh token
}

# Replay attempt (same rft_72ab... posted twice):
-> 400 invalid_grant
   "Refresh token replay detected. Session revoked."

When a replay is detected, the server does not just reject the bad request. It revokes the entire session lineage that the leaked token came from. Any other client that holds an active refresh token for that session has to re-authenticate. That is the correct response to a leaked token: assume the worst, force re-consent.

For ServiceNow workloads this matters because the cost of a leaked credential is real. A token with builds:write can spend your HealBudget. A token with projects:write can edit project metadata. Treating replay as an emergency is the only sane policy.

Yeti Build Agent run protected by OAuth 2.1 scoped access via SnowCoder MCP

Scopes That Mean Something

SnowCoder MCP exposes granular scopes rather than a single "use everything" permission. The current set:

  • mcp:read and mcp:write gate the MCP transport itself.
  • kb:read reaches the 100,000+ vector knowledge base and the 17,000+ code examples.
  • projects:read and projects:write manage project metadata.
  • builds:read and builds:write start, cancel, and inspect Yeti Build Agent runs.

The token issuance side is rigid. SnowCoder never silently upgrades a scope. If a client requests a tool that requires builds:write with a token that only carries builds:read, the response is a structured 403 with an actionable error. The user has to re-authorize with the widened scope set.

That rigidity is the security benefit. There is no path by which a low-privilege agent can convince the server to run a high-privilege operation.

Why PKCE, Specifically S256

Proof Key for Code Exchange started as a mobile-app protection and is now mandatory for OAuth 2.1 public clients. The reason: it removes the assumption that the redirect URI and the authorization code in transit are themselves enough to authenticate the redemption.

SnowCoder MCP requires the S256 method, not the legacy plain method. The challenge is the SHA-256 hash of the verifier, base64url-encoded. A bystander who intercepts the authorization code cannot redeem it without the original verifier, which never leaves the client.

The practical impact for ServiceNow workloads: if an attacker can read your loopback redirect URL, they still cannot turn an intercepted auth code into a valid token.

Dynamic Client Registration, and Why It Matters Here

RFC 7591 lets an MCP client register itself with the authorization server at runtime, without an admin minting credentials by hand. That is the only sane way to support a long tail of clients: Claude Code, Claude Desktop, Cursor, Continue, GCP Vertex AI, ChatGPT Desktop, OpenAI Codex, Grok. Pre-registering each one for each user does not scale.

SnowCoder MCP returns a client identifier and binds it to the user's account. The client identifier is not a credential on its own (the token endpoint accepts token_endpoint_auth_method: none in the public-client model). All authentication happens through PKCE and the user consent screen.

That model maps to ServiceNow operational reality: developers move between laptops, switch IDEs, and try new clients. Every new client gets a fresh registration, a fresh consent, and zero reuse of credentials.

Putting It All Together for ServiceNow

The cumulative posture, from the perspective of a ServiceNow instance behind SnowCoder MCP:

  • No long-lived shared secrets. Tokens are short-lived. Refresh tokens are single-use.
  • No silent privilege escalation. Scopes are enumerated, requested explicitly, and rejected when missing.
  • Compromise produces a loud signal. Refresh-token replay revokes the session lineage.
  • The MCP server, not the client, holds the credentials that reach the instance. A stolen MCP token still cannot become a ServiceNow OAuth grant.
  • Yeti Build Agent operations, which are the highest-impact MCP operations, are gated separately from KB read operations.

No single piece of this is unique to SnowCoder. The selection and combination are. The goal is a security model where the default failure mode is "agent does less," not "instance does something unauthorized."

What To Verify Before Production

If you are rolling SnowCoder MCP across a team, a small checklist worth keeping:

  • Confirm clients are using S256, not plain, for PKCE.
  • Confirm the scope sets in each client config match what users actually need (least privilege).
  • Confirm your secret store inside each IDE is encrypted at rest.
  • Test refresh-token replay handling in a dev account so you have seen the revocation behavior.
  • Wire MCP server access logs into your SIEM. Authorization errors are useful signal.

Related reading

Bring MCP to ServiceNow without weakening the perimeter.

SnowCoder MCP is on every tier with OAuth 2.1, PKCE, and scoped tokens. Pilot it or talk to us about an enterprise rollout.