How to Authenticate MCP Servers: OAuth, API Keys & Tokens (2026)
A complete guide to MCP server authentication ā API keys in env vars, OAuth flows, bearer tokens, and keeping secrets out of your config files.
How to Authenticate MCP Servers: OAuth, API Keys & Tokens (2026)
Most MCP servers need credentials to talk to external services ā an API key for GitHub, an OAuth token for Google Drive, a bot token for Slack. Getting authentication right is both the most important and most confusing part of MCP setup for newcomers.
This guide covers every authentication pattern you'll encounter across MCP servers: API keys, OAuth tokens, environment variables, and how to keep secrets secure.
The Three Authentication Patterns
MCP servers use three main credential types:
1. API Keys ā A static secret string issued by the service (GitHub PAT, OpenAI API key, etc.)
2. OAuth Access Tokens ā Time-limited tokens from an OAuth flow (Google, Microsoft, Salesforce)
3. Service Account Credentials ā JSON credential files (Google Cloud, GCP services)
How you pass them to the MCP server depends on the pattern.
---
Pattern 1: API Keys via Environment Variables
This is the most common pattern. The service gives you a static key; you pass it as an environment variable in your MCP config.
In Claude Desktop
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_yourTokenHere"
}
}
}
}
The env object in the MCP config is how you pass credentials. The server reads them as standard environment variables.
Multiple Servers with Different Keys
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_abc123"
}
},
"slack": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": {
"SLACK_BOT_TOKEN": "xoxb-xyz789",
"SLACK_TEAM_ID": "T1234567890"
}
}
}
}
Each server gets its own env block ā they don't share credentials.
---
Pattern 2: Loading from a .env File
Hardcoding secrets in claude_desktop_config.json works, but that file is on your filesystem in plaintext. A better approach for local development: use a .env file and reference it from a wrapper script.
Create a wrapper script
~/.mcp/github-wrapper.sh:
#!/bin/bash
source ~/.mcp/.env
exec npx -y @modelcontextprotocol/server-github "$@"
~/.mcp/.env:
GITHUB_PERSONAL_ACCESS_TOKEN=ghp_yourTokenHere
Make it executable:
chmod +x ~/.mcp/github-wrapper.sh
Claude Desktop config:
{
"mcpServers": {
"github": {
"command": "/Users/yourname/.mcp/github-wrapper.sh"
}
}
}
This keeps your actual secrets out of the Claude config file.
---
Pattern 3: OAuth Access Tokens
Some services use OAuth ā you complete a browser-based authorization flow and receive a time-limited access token. The MCP server then uses that token for API calls.
Google Services (Drive, Sheets, Calendar)
Google MCP servers typically use a credentials.json file from a Google Cloud service account, or an OAuth client flow.
Option A: Service Account (recommended for automation)
1. Go to Google Cloud Console
2. Create a project ā Enable the API you need (Drive, Sheets, etc.)
3. Go to IAM & Admin ā Service Accounts ā Create service account
4. Download the JSON key file
5. Reference it in your MCP config:
{
"mcpServers": {
"gdrive": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-gdrive"],
"env": {
"GOOGLE_APPLICATION_CREDENTIALS": "/Users/yourname/.mcp/google-service-account.json"
}
}
}
}
Option B: OAuth user token
Some Google MCP servers support the OAuth user flow. Run the server once interactively:
npx @modelcontextprotocol/server-gdrive --setup
This opens a browser window, you authorize, and a token file is saved locally. Subsequent runs use the saved token (and refresh it automatically).
Microsoft / Azure Services
Microsoft MCP integrations typically use Azure AD app registration:
1. Register an app in Azure Portal ā Azure Active Directory ā App Registrations
2. Add API permissions for the services you need (Graph API, etc.)
3. Create a client secret
4. Pass the credentials:
{
"mcpServers": {
"microsoft": {
"command": "npx",
"args": ["-y", "@your-org/mcp-server-microsoft"],
"env": {
"AZURE_CLIENT_ID": "your-client-id",
"AZURE_CLIENT_SECRET": "your-client-secret",
"AZURE_TENANT_ID": "your-tenant-id"
}
}
}
}
---
Pattern 4: Bearer Tokens
Some APIs use a simple bearer token passed in an Authorization header. Custom MCP servers built on these APIs typically accept the token as an environment variable:
{
"mcpServers": {
"custom-api": {
"command": "node",
"args": ["/path/to/custom-mcp-server/index.js"],
"env": {
"API_BEARER_TOKEN": "Bearer your-token-here"
}
}
}
}
---
Keeping Secrets Secure
What Not to Do
ā Don't commit your MCP config to a public repo. The config file at ~/Library/Application Support/Claude/claude_desktop_config.json contains your API keys in plaintext. If you back up your dotfiles to GitHub, exclude this file.
Add to your .gitignore
Library/Application Support/Claude/claude_desktop_config.json
ā Don't share your config file directly. If someone asks "can you share your MCP config?", scrub all credentials first.
ā Don't use overly-permissive tokens. If your GitHub PAT only needs repo read access, don't grant it full admin rights. Scope tokens to the minimum needed.
What to Do
ā Use token scoping. Create tokens with minimal permissions for what the MCP server actually needs.
ā Rotate tokens periodically. Most services let you set expiration dates on tokens. Use them.
ā Use separate tokens for MCP vs other tools. If your GitHub MCP token is compromised, you don't want it to be the same token you use for CI/CD.
ā
Store sensitive configs in a keychain-backed secret. On Mac, you can store the token in Keychain and reference it via a shell script that reads from security find-generic-password.
#!/bin/bash
export GITHUB_PERSONAL_ACCESS_TOKEN=$(security find-generic-password -a "$USER" -s "github-mcp-token" -w)
exec npx -y @modelcontextprotocol/server-github "$@"
---
Troubleshooting Authentication Errors
"Authentication failed" / 401 Unauthorized
"Permission denied" / 403 Forbidden
repo scope if accessing private reposToken Works in CLI but Not in MCP
Environment variables in your shell don't automatically pass into MCP server processes ā they must be explicitly listed in the env block of your MCP config. You can't rely on variables set in .bashrc or .zshrc.
OAuth Token Expired
OAuth access tokens typically expire after 1 hour. If your MCP server uses a stored OAuth token (common with Google integrations), look for a --refresh flag or re-run the setup flow to get a fresh token.
---
Quick Reference: Common MCP Servers and Their Auth Variables
| MCP Server | Auth Variable | Where to Get It |
|------------|--------------|-----------------|
| GitHub | GITHUB_PERSONAL_ACCESS_TOKEN | github.com ā Settings ā Developer Settings ā PATs |
| Slack | SLACK_BOT_TOKEN, SLACK_TEAM_ID | api.slack.com ā Your App ā OAuth & Permissions |
| Google Drive | GOOGLE_APPLICATION_CREDENTIALS | Google Cloud Console ā Service Accounts |
| Supabase | SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY | Supabase project ā Settings ā API |
| Postgres | POSTGRES_CONNECTION_STRING | Your database connection string |
| Notion | NOTION_API_KEY | notion.so/my-integrations |
| OpenAI | OPENAI_API_KEY | platform.openai.com ā API Keys |
| Atlassian | ATLASSIAN_API_TOKEN, ATLASSIAN_EMAIL, ATLASSIAN_BASE_URL | id.atlassian.com ā Security ā API Tokens |
---