shiftrightlabs/vscode-mcp-auth-sample
If you are the rightful owner of vscode-mcp-auth-sample 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 project demonstrates how to build a secure Model Context Protocol (MCP) server with OAuth 2.1 authentication, specifically designed for integration with Visual Studio Code.
VS Code MCP Authentication Sample
A complete reference implementation showing how to build a secure MCP (Model Context Protocol) server with OAuth 2.1 authentication for VS Code.
🎯 What This Project Does
This project demonstrates how to build a production-ready MCP server that runs locally on the user's machine, integrates with VS Code, and uses OAuth 2.1 authentication (PKCE flow) with Azure AD as the identity provider.
Key Features:
- ✅ Designed for local deployment (runs on user's machine alongside VS Code)
- ✅ Full OAuth 2.1 Authorization Code flow with PKCE (no client secret needed)
- ✅ MCP server implementation using official
@modelcontextprotocol/sdk - ✅ HTTP transport - works with VS Code's MCP client
- ✅ Token validation via Microsoft Graph API introspection
- ✅ Production-ready security patterns
- ✅ TypeScript with full type safety
Deployment Model: This server is designed to run locally (like http://localhost:3000). For remote server deployments, see the "How We Implement OAuth" section below.
Important Note: VS Code uses its own hardcoded client ID (aebc6443-996d-45c2-90f0-388ff96faa56, defined in extensions/microsoft-authentication/src/AADHelper.ts) when obtaining tokens, not your MCP server's client ID. This means you'll receive Microsoft Graph API tokens instead of tokens scoped to your application. See "Challenge #2" below for the full technical explanation with source code evidence and solution.
📖 The Story: How Everything Works Together
The Challenge
Building a production-ready MCP server with OAuth authentication that works with VS Code is complex. While the MCP specification defines OAuth support, there are very few real-world examples showing how to implement it for VS Code integration. VS Code's MCP client has specific behaviors (like using its own client ID) that aren't documented elsewhere. This project solves two major challenges that developers face when building OAuth-authenticated MCP servers for VS Code:
1. Lack of OAuth + MCP + VS Code Integration Examples
The Problem: While the MCP specification includes OAuth support, there are almost no complete, working examples showing how to implement OAuth-authenticated MCP servers that integrate with VS Code. Most documentation focuses on the MCP protocol itself or generic OAuth flows, but doesn't address:
- How to handle VS Code's specific OAuth implementation
- How VS Code's MCP client behaves during authentication
- How to deal with VS Code using its own client ID (see Challenge #2)
- How to validate the Microsoft Graph API tokens that VS Code sends
Our Solution: This project provides a complete, production-ready reference implementation specifically for VS Code integration, showing:
- How to implement the OAuth 2.1 Authorization Code flow with PKCE for VS Code
- How to integrate OAuth metadata discovery (RFC 9728) that VS Code recognizes
- How to protect MCP endpoints with Bearer token authentication
- How to handle VS Code's client ID behavior and Graph API tokens
- How to manage the complete authentication lifecycle with VS Code's MCP client
2. Microsoft Graph API Token Validation (The Biggest Surprise)
The Problem: This is the most surprising challenge. You might expect that when VS Code authenticates users through your MCP server's OAuth flow, it would use your MCP server's client ID to obtain tokens. But that's not what happens.
The Surprise: VS Code Uses Its Own Client ID
Instead, VS Code uses its own hardcoded client ID (aebc6443-996d-45c2-90f0-388ff96faa56) to obtain tokens, completely ignoring your MCP server's registered client ID.
Evidence from VS Code Source Code:
This behavior is hardcoded in VS Code's Microsoft authentication extension:
- File:
extensions/microsoft-authentication/src/AADHelper.ts(GitHub) - Constant:
DEFAULT_CLIENT_ID = 'aebc6443-996d-45c2-90f0-388ff96faa56' - Behavior: VS Code's
getClientId()method uses this default unless you pass a special scope markerVSCODE_CLIENT_ID:your-id, which is non-standard OAuth and not what MCP servers do
How It Works:
When VS Code detects that your MCP server uses Microsoft Entra (Azure AD) for OAuth, it uses its built-in Microsoft authentication provider. This provider calls vscode.authentication.getSession('microsoft', scopes), which always uses VS Code's hardcoded client ID for token requests.
Related GitHub Issues:
- #115626 - Microsoft Auth Provider should support overriding client ID
- #248775 - API to map auth server to auth provider (for MCP)
- #252892 - Feature: VSCode capability to register a clientId for MCP OAuth
What This Means:
- ✅ The OAuth flow uses your MCP server's authorization endpoint
- ✅ The user authenticates via your Azure AD tenant
- ❌ But the access token is issued for VS Code's application, not yours
- ❌ The token audience is Microsoft Graph API (
00000003-0000-0000-c000-000000000000), not your MCP server
Authentication Flow (What Actually Happens):
1. MCP Server registers with: AZURE_CLIENT_ID=your-server-id
2. VS Code detects Microsoft Entra as the IdP
3. VS Code uses built-in Microsoft authentication provider
4. Provider calls: authentication.getSession('microsoft', scopes)
5. Internally uses: DEFAULT_CLIENT_ID = 'aebc6443-996d-45c2-90f0-388ff96faa56'
6. Azure AD issues token for VS Code's application
7. Token audience = '00000003-0000-0000-c000-000000000000' (Graph API)
8. MCP server receives this Graph API token, not a token for your app
When you decode the token, you'll see:
{
"aud": "00000003-0000-0000-c000-000000000000", // Microsoft Graph API
"appid": "aebc6443-996d-45c2-90f0-388ff96faa56", // VS Code's client ID
"iss": "https://sts.windows.net/{your-tenant}/", // Your tenant
"scp": "User.Read openid profile email" // Graph API scopes
}
Why This Matters:
These Microsoft Graph API tokens (with audience 00000003-0000-0000-c000-000000000000) cannot be validated using standard JWT signature validation by third-party services - even with the correct JWKS signing keys from Azure AD. This is intentional by Microsoft to prevent Graph API tokens from being misused by services other than Microsoft Graph.
Most developers try the standard approach and get stuck:
// ❌ This approach fails with "invalid signature"
jwt.verify(token, getSigningKey, {
issuer: 'https://sts.windows.net/{tenant}/',
audience: '00000003-0000-0000-c000-000000000000'
});
// Error: invalid signature (even though the key is correct!)
Our Solution: Token Introspection via Graph API
Since we can't validate the signature locally, we validate tokens by calling the Microsoft Graph API itself. If the API accepts the token and returns user data, we know it's valid:
// ✅ This works - Microsoft validates the token on their end
const response = await axios.get('https://graph.microsoft.com/v1.0/me', {
headers: { 'Authorization': `Bearer ${token}` },
});
// Success (200) = token is valid, returns user profile
// Failure (401) = token is invalid or expired
Why This Approach is Correct:
- ✅ Microsoft performs cryptographic validation on their servers
- ✅ We get user profile information as a bonus
- ✅ Catches expired or revoked tokens immediately
- ✅ This is the official Microsoft-recommended approach for Graph API tokens
- ✅ We cache validated tokens (5 min TTL) for performance
Summary:
The key insight is that VS Code doesn't use your MCP server's client ID - it uses its own hardcoded client ID (aebc6443-996d-45c2-90f0-388ff96faa56) to obtain Microsoft Graph API tokens. This is by design in VS Code's authentication system and is documented in multiple GitHub issues (#115626, #248775, #252892). As a result, your MCP server must validate these Graph API tokens by calling the Microsoft Graph API, not by using standard JWT signature validation. This project demonstrates the correct approach.
How We Implement OAuth (PKCE for Local Deployment)
Since this MCP server runs locally on the user's machine (like http://localhost:3000), we use OAuth 2.1 with PKCE (Proof Key for Code Exchange) instead of client secrets.
Why PKCE for Local Deployment:
When your server code runs on the user's machine, any client secret in the code would be accessible to users. PKCE solves this by using cryptographic challenge/response pairs instead of secrets:
User → VS Code → MCP Server → Azure AD
↓
PKCE Challenge
↓
Azure AD Login
↓
Authorization Code
↓
Exchange for Access Token
PKCE Benefits:
- ✅ No client secret needed (safe for local deployment)
- ✅ Cryptographic security via code challenge/verifier pair
- ✅ Standard OAuth 2.1 for public clients
- ✅ Same pattern used by mobile apps, SPAs, desktop apps
Alternative for Remote Servers:
If you're deploying an MCP server on a remote, secure server (not locally), you could use the traditional Confidential Client flow with a client secret instead. The secret would be safe because it stays on your secure server, not accessible to end users.
🏗️ Architecture
┌─────────────┐
│ VS Code │
│ (Client) │
└──────┬──────┘
│
│ 1. User triggers authentication
↓
┌─────────────────────────────────────────┐
│ MCP Server (This Project) │
│ │
│ ┌───────────────────────────────────┐ │
│ │ OAuth 2.1 PKCE Flow │ │
│ │ - /authorize (redirect to Azure) │ │
│ │ - /callback (exchange code) │ │
│ │ - PKCE challenge generation │ │
│ └───────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────┐ │
│ │ MCP HTTP Transport │ │
│ │ - POST /mcp (JSON-RPC) │ │
│ │ - GET /mcp (SSE) │ │
│ │ - Session management │ │
│ └───────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────┐ │
│ │ Authentication Middleware │ │
│ │ - Token structure validation │ │
│ │ - Graph API introspection │ │
│ │ - Token caching (5 min TTL) │ │
│ └───────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────┐ │
│ │ MCP Tools │ │
│ │ - get-user-info │ │
│ │ - echo │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
│ │
│ │ 3. Validate token
│ ↓
│ ┌─────────────────┐
│ │ Microsoft Graph │
│ │ API │
│ └─────────────────┘
│
│ 2. OAuth flow
↓
┌─────────────┐
│ Azure AD │
│ (IdP) │
└─────────────┘
🚀 Quick Start
Prerequisites
- Node.js 18+ and npm
- Azure AD tenant (free tier works)
- VS Code with MCP support
1. Clone and Install
git clone https://github.com/shiftrightlabs/vscode-mcp-auth-sample.git
cd vscode-mcp-auth-sample
npm install
2. Azure AD Setup
-
Go to Azure Portal → Azure Active Directory → App registrations
-
Click New registration:
- Name:
MCP Server Auth Sample - Supported account types: Accounts in this organizational directory only
- Redirect URI:
- Platform: Web
- URI:
http://localhost:3000/callback
- Click Register
- Name:
-
After registration:
- Copy Application (client) ID
- Copy Directory (tenant) ID
-
Configure Authentication:
- Go to Authentication in the left menu
- Under Redirect URIs, add:
http://localhost:3000/callbackhttp://127.0.0.1:3000/callback
- Under Implicit grant and hybrid flows, check:
- ✅ ID tokens
- Click Save
-
Configure API permissions:
- Go to API permissions in the left menu
- Click Add a permission → Microsoft Graph → Delegated permissions
- Add these permissions:
User.Readopenidprofileemail
- Click Add permissions
- Click Grant admin consent (if you have admin rights)
3. Environment Configuration
Create a .env file in the root directory:
cp .env.example .env
Edit .env and fill in your Azure AD credentials:
# Azure AD Configuration
AZURE_CLIENT_ID=your-application-client-id-here
AZURE_TENANT_ID=your-directory-tenant-id-here
# Server Configuration
PORT=3000
SERVER_URL=http://localhost:3000
Important:
AZURE_CLIENT_SECRETis NOT required - this is a public client using PKCE- Never commit
.envto version control (already in.gitignore)
4. Build and Run
# Build TypeScript
npm run build
# Start the server
npm start
You should see:
MCP Server with OAuth running on http://localhost:3000
OAuth endpoints:
- Authorization: http://localhost:3000/authorize
- Callback: http://localhost:3000/callback
- OAuth Metadata: http://localhost:3000/.well-known/oauth-protected-resource
MCP endpoints:
- POST /mcp (JSON-RPC)
- GET /mcp (SSE)
5. VS Code Configuration
Add to your VS Code settings.json:
{
"mcp.servers": {
"mcp-auth-sample": {
"url": "http://localhost:3000/mcp",
"authorization": {
"type": "oauth2",
"authorizationUrl": "http://localhost:3000/authorize"
}
}
}
}
6. Test the Authentication
- Open VS Code
- Open the MCP panel (View → MCP or Ctrl+Shift+M)
- You should see "mcp-auth-sample" server
- Click Authenticate
- A browser window opens → Sign in with your Microsoft account
- After successful authentication, you should see:
✅ Discovered 2 tools
7. Test the Tools
Once authenticated, you can test the MCP tools through GitHub Copilot or any AI assistant that supports MCP.
Tool: get-user-info
Returns authenticated user information from access token and Microsoft Graph API.
Example prompts to trigger this tool:
- "Show me my authenticated user information"
- "What are my user details from the access token?"
- "Get my profile information from Microsoft Graph API"
Output:
{
"title": "✅ Authenticated User Information",
"authentication": {
"method": "OAuth 2.0 Authorization Code with PKCE",
"client_type": "Public Client (no client secret)",
"token_validated": "via Microsoft Graph API introspection"
},
"token_claims": {
"issuer": "https://sts.windows.net/{tenant}/",
"subject": "...",
"audience": "00000003-0000-0000-c000-000000000000",
"scopes": ["User.Read", "openid", "profile", "email"]
},
"graph_api_user": {
"displayName": "Your Name",
"mail": "your.email@example.com",
"id": "...",
"userPrincipalName": "you@example.com"
}
}
Tool: echo
Echoes a message back with authentication confirmation.
Example prompts to trigger this tool:
- "Echo this message: Hello, MCP!"
- "Test the echo tool with 'Authentication is working'"
- "Can you echo back 'OAuth 2.1 PKCE is awesome'?"
Output:
Echo: Hello, MCP!
Authenticated via OAuth 2.0 PKCE
📁 Project Structure
vscode-mcp-auth-sample/
├── src/
│ ├── server.ts # Express app entry point
│ ├── config.ts # Configuration and validation
│ ├── oauth.ts # OAuth 2.1 PKCE flow
│ ├── metadata.ts # RFC 9728 metadata endpoint
│ ├── middleware/
│ │ └── auth.ts # Token validation middleware
│ └── mcp/
│ └── http-transport.ts # MCP HTTP transport & tools
├── dist/ # Compiled JavaScript (generated)
├── .env.example # Environment template
├── .env # Your credentials (git-ignored)
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── CLAUDE.md # Project documentation for Claude Code
└── README.md # This file
🔐 Security Model
OAuth 2.1 Public Client with PKCE
This implementation uses PKCE (Proof Key for Code Exchange) instead of client secrets:
// 1. Generate code verifier (random string)
const codeVerifier = crypto.randomBytes(32).toString('base64url');
// 2. Generate code challenge (SHA256 hash)
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
// 3. Send challenge to Azure AD
const authUrl = `${authorizeUrl}?
client_id=${clientId}&
code_challenge=${codeChallenge}&
code_challenge_method=S256&
...`;
// 4. Azure AD validates: SHA256(code_verifier) === code_challenge
Benefits:
- ✅ No client secret needed (safe for local deployment)
- ✅ Cryptographic security via challenge/verifier pair
- ✅ Suitable for applications running on user machines (MCP servers, SPAs, mobile apps, desktop apps)
- ✅ Required by OAuth 2.1 specification for public clients
Deployment Context: This server is designed to run locally (e.g., http://localhost:3000 on the user's machine). For remote server deployments where code isn't accessible to users, you could use a Confidential Client flow with a client secret instead.
Token Validation Strategy
Challenge: Microsoft Graph API tokens cannot be validated using standard JWT signature validation.
Solution: Token introspection via Graph API:
// 1. Validate token structure (issuer, expiration)
const payload = jwt.decode(token);
validateIssuer(payload.iss);
validateExpiration(payload.exp);
// 2. Introspect via Microsoft Graph API
const response = await axios.get('https://graph.microsoft.com/v1.0/me', {
headers: { 'Authorization': `Bearer ${token}` }
});
// 3. Cache validated tokens (5-minute TTL)
tokenCache.set(token, { user: response.data, validUntil: now + 5min });
Security Benefits:
- ✅ Real cryptographic validation (Microsoft validates server-side)
- ✅ User profile information obtained
- ✅ Token freshness check (catches expired/revoked tokens)
- ✅ Production-ready (no signature validation bypass)
🛠️ Development
Available Scripts
# Development mode (auto-reload with ts-node)
npm run dev
# Build TypeScript to JavaScript
npm run build
# Run compiled JavaScript
npm start
# Watch mode (auto-compile on changes)
npm run watch
Tech Stack
- Runtime: Node.js 18+
- Language: TypeScript 5.3+
- Web Framework: Express 4.x
- MCP SDK:
@modelcontextprotocol/sdk1.20+ - HTTP Client: Axios
- Validation: Zod
- Token Handling: jsonwebtoken
Key Dependencies
{
"@modelcontextprotocol/sdk": "^1.20.1", // Official MCP SDK
"express": "^4.18.2", // Web server
"axios": "^1.6.5", // HTTP client (Graph API)
"jsonwebtoken": "^9.0.2", // JWT decoding
"zod": "^3.25.76" // Schema validation
}
📚 Learn More
Understanding the Code
The best way to understand how everything works is to follow the request flow:
-
OAuth Flow ()
/authorize- Generates PKCE challenge, redirects to Azure AD/callback- Validates PKCE verifier, exchanges code for token
-
MCP Transport ()
- Creates per-session transport instances
- Handles three endpoints: POST, GET (SSE), DELETE
- Manages session IDs and cleanup
-
Authentication ()
- Validates token structure and basic claims
- Calls Microsoft Graph API for introspection
- Caches validated tokens for performance
-
MCP Tools ()
get-user-info- Demonstrates token reading + Graph API accessecho- Simple authenticated tool
Standards & Specifications
This implementation follows:
- MCP Authorization Specification (2025-06-18)
- OAuth 2.1 - Authorization framework with PKCE
- RFC 7636 - PKCE for OAuth 2.0
- RFC 9728 - OAuth 2.0 Protected Resource Metadata
- RFC 6750 - Bearer token usage
Why This Approach?
Why HTTP transport instead of stdio?
- VS Code's MCP client uses HTTP, not stdio
- HTTP allows multiple concurrent clients
- Better for production deployments
Why PKCE instead of client secret?
- This MCP server runs locally on the user's machine (code is accessible to users)
- Client secrets can't be safely stored in code running on user machines
- PKCE provides cryptographic security without secrets
- Required by OAuth 2.1 for public clients
- Note: Remote servers could use client secrets (Confidential Client flow)
Why Graph API introspection instead of JWT validation?
- Microsoft Graph API tokens are opaque to third parties by design
- Standard JWT signature validation fails (even with correct keys)
- Graph API introspection is the official Microsoft-recommended approach
🤝 Contributing
Contributions are welcome! This is an educational project to help developers understand MCP + OAuth integration.
How to Contribute
- Fork the repository at https://github.com/shiftrightlabs/vscode-mcp-auth-sample
- Clone your fork (
git clone https://github.com/YOUR-USERNAME/vscode-mcp-auth-sample.git) - Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes and commit (
git commit -m 'Add amazing feature') - Push to your fork (
git push origin feature/amazing-feature) - Open a Pull Request at https://github.com/shiftrightlabs/vscode-mcp-auth-sample/pulls
📄 License
This project is licensed under the MIT License - see the file for details.
🙏 Acknowledgments
- Anthropic - For creating and maintaining the Model Context Protocol (MCP) specification and SDK
- Microsoft - For Azure AD OAuth 2.0 platform and Microsoft Graph API documentation
❓ Frequently Asked Questions
Q: Why do I need to register an Azure AD application if VS Code uses its own client ID?
A: Great question! Even though VS Code uses its own client ID to obtain the access token, you still need to register your own Azure AD application because:
- Your MCP server's authorization endpoint (
/authorize) needs a client ID for the OAuth flow - The tenant ID from your app registration determines which Azure AD tenant users authenticate against
- Your redirect URI configuration (
http://localhost:3000/callback) must match your app registration
Think of it this way: Your app registration controls which tenant and which redirect URIs are allowed. VS Code then leverages that OAuth flow but substitutes its own client ID when requesting the token.
Q: Can I make VS Code use my MCP server's client ID instead of its own?
A: No, this is hardcoded in VS Code's Microsoft authentication extension (extensions/microsoft-authentication/src/AADHelper.ts). The constant DEFAULT_CLIENT_ID = 'aebc6443-996d-45c2-90f0-388ff96faa56' is used by default. While technically the code supports a VSCODE_CLIENT_ID: scope marker to override this, that's a non-standard OAuth pattern that MCP servers don't use. For more details, see GitHub issue #115626.
Q: Why does VS Code do this?
A: This allows VS Code to obtain tokens with Microsoft Graph API scopes (like User.Read) that it can use for its own features (like account management, settings sync, etc.), while still authenticating users through your MCP server's OAuth flow. It's a design decision that lets VS Code's built-in authentication providers work consistently across different extensions and MCP servers, but it means MCP servers must handle Graph API tokens rather than tokens scoped to their own application.
Q: Does this mean my client ID configuration is ignored?
A: Your AZURE_CLIENT_ID is still important! It's used for:
- The OAuth metadata endpoint (
/.well-known/oauth-protected-resource) - Configuring which Azure AD tenant to use (via tenant ID)
- Setting up the redirect URI in Azure AD
Even though the final access token uses VS Code's client ID, your configuration still controls the authentication flow.
Q: Is this a security issue?
A: No, this is by design. The tokens are still:
- Issued by your Azure AD tenant (controlled by your tenant ID)
- Validated via Microsoft Graph API (cryptographically secure)
- Scoped to the authenticated user
- Only usable for Microsoft Graph API calls (audience restriction)
The Graph API introspection pattern we use is Microsoft's recommended approach for validating these tokens.
💬 Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
🔗 Resources
Built with ❤️ by the ShiftRight Labs team to help developers build secure MCP servers.