yannickepstein/secure-mcp
If you are the rightful owner of secure-mcp and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.
This document provides a structured overview of a Model Context Protocol (MCP) server, focusing on OAuth audience validation as per RFC 8707.
Secure MCP Demo - OAuth Audience Validation
A minimal reproducible example demonstrating proper OAuth audience validation in MCP (Model Context Protocol) servers per RFC 8707.
The Problem
Some MCP clients fail to send the resource or audience parameter when requesting OAuth tokens, violating RFC 8707 and the MCP specification, breaking audience-restricted access tokens.
What Should Happen (RFC 8707 Compliant)
- MCP server advertises:
"resource": "https://example.com/google-drive-mcp" - Client includes resource in authorization request:
resource=https://example.com/google-drive-mcp - Client includes resource in token request:
resource=https://example.com/google-drive-mcp - Token is issued with matching
audclaim - Token validation succeeds ✅
What's Happening (Windsurf Bug)
- MCP server advertises:
"resource": "https://example.com/google-drive-mcp" - Client omits resource in authorization request ❌
- Client omits resource and audience in token request ❌
- Token is issued with generic audience (just the origin)
- Token validation fails because
audclaim doesn't match the MCP server's resource URI
Why This Matters
Per RFC 9728 Section 7.4, audience-restricted access tokens prevent a token issued for one resource server from being used on another. Without proper audience validation, a token for one MCP server could be reused on a different MCP server at the same host.
Real-World Test Results
This demo has been tested with multiple MCP clients. Here are the actual results:
✅ Cursor - RFC 8707 Compliant
[OAuth Server] Authorization request received:
Resource: http://localhost:3000/google-drive-mcp ✅
[OAuth Server] Token request received:
Requested Resource: http://localhost:3000/google-drive-mcp ✅
[MCP Server] Token validation:
Expected audience: http://localhost:3000/google-drive-mcp
Actual audience: http://localhost:3000/google-drive-mcp
✅ VALIDATION SUCCESSFUL
Result: Cursor successfully connects and can use all MCP features.
❌ Windsurf - RFC 8707 Violations
[OAuth Server] Authorization request received:
Resource: undefined ❌ NOT SENT
[OAuth Server] Token request received:
Requested Audience: NOT PROVIDED ❌
Requested Resource: NOT PROVIDED ❌
[MCP Server] Token validation:
Expected audience: http://localhost:3000/google-drive-mcp
Actual audience: http://localhost:3000
❌ VALIDATION FAILED: Audience mismatch!
Result: Windsurf fails to connect. Token validation fails because no resource parameter was sent.
Specification Violations (Windsurf)
- Missing resource in authorization request - Violates MCP spec section 2.5.1
- Missing resource/audience in token request - Violates RFC 8707 section 2
- Wrong discovery endpoint - Requests
/.well-known/oauth-protected-resourceinstead of/.well-known/oauth-protected-resource/google-drive-mcp
Installation
npm install
Usage
Start the Demo Server
npm install
npm run dev
This starts a combined server containing:
- Mock OAuth Authorization Server - Issues JWT tokens with requested audience
- Mock MCP Server - Implements MCP JSON-RPC protocol with OAuth validation
Quick Test with Automated Script
./test.sh
This script demonstrates:
- ✅ Token with correct audience (
http://localhost:3000/google-drive-mcp) → Request succeeds - ❌ Token with path stripped (
http://localhost:3000) → Request fails with audience mismatch
Test with a Real MCP Client
The server implements a proper MCP server that can be tested with real MCP clients (Claude Code, Cursor, Windsurf, etc.).
Add this configuration to your MCP client settings:
{
"mcpServers": {
"secure-mcp-demo": {
"url": "http://localhost:3000/google-drive-mcp",
"transport": "http"
}
}
}
When the MCP client connects, it will:
- Attempt to call the MCP server without a token
- Receive a 401 response with
WWW-Authenticateheader containing the resource URI - Discover OAuth metadata from
.well-known/oauth-protected-resource - Request a token from the OAuth server with the
audienceparameter - Retry the MCP request with the token
Watch the server logs to see what each client does. The server logs every request with detailed information about OAuth parameters, making it easy to identify specification compliance issues.
Relevant Specifications
MCP Specification
MCP Authorization - Section 2.5.1:
MCP clients MUST implement Resource Indicators for OAuth 2.0 as defined in RFC 8707... The resource parameter MUST use the canonical URI of the MCP server as defined in RFC 8707 Section 2.
RFC 8707 - Resource Indicators for OAuth 2.0
The value of the "resource" parameter MUST be an absolute URI.
The specification requires using the full canonical URI as the resource identifier.
RFC 9728 - OAuth 2.0 Security Best Practices
If a client expects to interact with multiple resource servers, the client SHOULD request audience-restricted access tokens using [RFC8707].
Project Structure
secure-mcp/
├── src/
│ └── server.ts # Combined OAuth + MCP server
├── package.json
├── tsconfig.json
└── README.md
How It Works
- MCP Client makes initial request without authentication to
/google-drive-mcp - Server responds with 401 including
WWW-Authenticate: Bearer resource="http://localhost:3000/google-drive-mcp" - Client discovers OAuth metadata from
/.well-known/oauth-protected-resource/google-drive-mcp - Client discovers OAuth server from
/.well-known/oauth-authorization-server - Client registers dynamically (optional, RFC 7591) via
/registerendpoint - Client initiates authorization with
resourceparameter in the authorization request - Client requests token from OAuth server with
resourceoraudienceparameter- ✅ Compliant client (Cursor): Sends
resource=http://localhost:3000/google-drive-mcp - ❌ Non-compliant client (Windsurf): Omits both
resourceandaudienceparameters
- ✅ Compliant client (Cursor): Sends
- OAuth server issues token with the requested audience in the
audclaim - Client retries MCP request with
Authorization: Bearer <token> - Server validates token and checks if
audclaim matches the resource URI exactly- ✅ If match: Request succeeds
- ❌ If mismatch: Returns 403 with detailed error message
Implemented OAuth Features
- ✅ Dynamic Client Registration (RFC 7591) - Automatic client registration
- ✅ Resource Indicators (RFC 8707) - Audience-restricted tokens
- ✅ PKCE (RFC 7636) - Proof Key for Code Exchange support
- ✅ Authorization Code Flow - Full OAuth 2.1 flow
- ✅ JWT Access Tokens (RFC 9068) - Structured access tokens with
at+jwttype - ✅ OAuth Discovery (RFC 8414) -
.well-known/oauth-authorization-server - ✅ Protected Resource Metadata -
.well-known/oauth-protected-resourceper MCP spec
License
Apache-2.0