MCP authentication errors like '401 Unauthorized', 'Invalid API key', and 'Permission denied' happen when the server cannot validate credentials. The fix depends on the cause: check that the env block in your host config has the correct variable names and values, verify the API key is still valid and not expired, and ensure the key has the required permissions. For remote Streamable HTTP servers, OAuth 2.1 authentication issues require checking token expiry and redirect URIs.
Fixing MCP Authentication Errors
MCP servers that connect to external services (GitHub, Slack, databases) need API keys or tokens passed as environment variables. When these credentials are missing, expired, or misconfigured, the server returns authentication errors. This tutorial covers every common auth error pattern and walks you through diagnosing and fixing each one.
Prerequisites
- An MCP server showing authentication errors
- Access to the API key or token the server needs
- Access to your MCP host configuration file
Step-by-step guide
Identify the exact authentication error
Identify the exact authentication error
Authentication errors appear in different forms depending on the server and the external service it connects to. Check your MCP host logs and the server's stderr output for the specific error message. The error message tells you whether the credential is missing entirely, present but invalid, or valid but lacking required permissions.
1# Common authentication error messages:23# Missing credentials:4# "Error: API_KEY environment variable is not set"5# "Missing required configuration: GITHUB_PERSONAL_ACCESS_TOKEN"67# Invalid credentials:8# "401 Unauthorized"9# "Invalid API key"10# "Bad credentials"11# "Authentication failed"1213# Insufficient permissions:14# "403 Forbidden"15# "Permission denied"16# "Insufficient scopes. Required: repo, read:org"17# "Resource not accessible by personal access token"1819# Check logs:20tail -n 30 ~/Library/Logs/Claude/mcp*.log | grep -i -E "auth|401|403|key|token|permission"Expected result: You have identified the specific authentication error and can determine if the issue is missing, invalid, or insufficient credentials.
Verify the env block in your host config
Verify the env block in your host config
The most common cause is a missing or misnamed environment variable in the host config. Open your claude_desktop_config.json or .cursor/mcp.json and check the env block for the failing server. Verify the variable name matches exactly what the server expects (names are case-sensitive). Check the server's README or documentation for the correct variable names.
1// Check your config matches the server's expected variable names23// GitHub server expects:4{5 "env": {6 "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxxxxxxxxxxx"7 }8}9// NOT: "GITHUB_TOKEN" or "GH_TOKEN" — the name must match exactly1011// Slack server expects:12{13 "env": {14 "SLACK_BOT_TOKEN": "xoxb-xxxxxxxxxxxx",15 "SLACK_TEAM_ID": "T01234567"16 }17}1819// Common pattern — check the server's documentation:20// @modelcontextprotocol/server-github → GITHUB_PERSONAL_ACCESS_TOKEN21// @modelcontextprotocol/server-slack → SLACK_BOT_TOKEN + SLACK_TEAM_ID22// @modelcontextprotocol/server-brave-search → BRAVE_API_KEYExpected result: Your env block has the correct variable names and values for the server you are configuring.
Validate the API key is still active
Validate the API key is still active
API keys and tokens can expire, be revoked, or reach usage limits. Test the key independently of MCP by making a direct API call with curl or your browser. For GitHub tokens, check the token's status on github.com/settings/tokens. For other services, check their respective dashboards. If the key is expired, generate a new one and update your MCP config.
1# Test a GitHub token directly:2curl -H "Authorization: token ghp_your_token_here" \3 https://api.github.com/user4# Should return your user profile JSON. 401 = invalid token.56# Test a Brave Search API key:7curl "https://api.search.brave.com/res/v1/web/search?q=test" \8 -H "X-Subscription-Token: your-brave-key"9# Should return search results. 401 = invalid key.1011# Check GitHub token permissions:12curl -H "Authorization: token ghp_your_token_here" \13 https://api.github.com/rate_limit14# Shows rate limit info if token is validExpected result: The API call succeeds with the key, confirming it is valid, or fails with a clear error indicating it needs to be regenerated.
Check token permissions and scopes
Check token permissions and scopes
Even a valid token can fail if it lacks the required permissions. GitHub Personal Access Tokens need specific scopes (like repo for repository access, read:org for organization data). The error message often tells you which scope is missing. Generate a new token with the correct scopes, or edit the existing token's permissions in the service's dashboard.
1# GitHub token scopes needed for the MCP GitHub server:2# - repo (full repository access)3# - read:org (read organization data)4# - gist (optional, for gist operations)56# To create a new GitHub token with correct scopes:7# 1. Go to github.com/settings/tokens8# 2. Click 'Generate new token (classic)'9# 3. Select scopes: repo, read:org10# 4. Generate and copy the token11# 5. Update your MCP config env block1213# The error message often tells you what is needed:14# "Insufficient scopes. Required: repo, read:org"15# → Your token is missing those scopesExpected result: Your token has the required scopes and permissions for all operations the MCP server needs.
Debug credential passing with a startup check
Debug credential passing with a startup check
If you are building your own MCP server and users report auth errors, add a startup check that verifies credentials are present and valid before the server begins accepting connections. This gives users an immediate, clear error instead of a confusing failure on the first tool call. If you are dealing with complex authentication setups involving OAuth, SSO, or multiple services, the RapidDev team can help design a secure credential management strategy for your MCP servers.
1// Add to your server's entry point2async function validateCredentials() {3 const apiKey = process.env.API_KEY;4 if (!apiKey) {5 console.error("ERROR: API_KEY environment variable is required.");6 console.error("Add it to your MCP host config:");7 console.error(' "env": { "API_KEY": "your-key-here" }');8 process.exit(1);9 }1011 // Optional: test the key with a lightweight API call12 try {13 const res = await fetch("https://api.example.com/ping", {14 headers: { Authorization: `Bearer ${apiKey}` },15 });16 if (!res.ok) {17 console.error(`ERROR: API key validation failed (${res.status})`);18 console.error("Check that your API key is valid and not expired.");19 process.exit(1);20 }21 console.error("API key validated successfully.");22 } catch (error) {23 console.error("WARNING: Could not validate API key:", error.message);24 }25}2627await validateCredentials();Expected result: The server validates credentials at startup and provides clear error messages if they are missing or invalid.
Complete working example
1import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";2import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";3import { z } from "zod";45// Validate credentials at startup6const API_KEY = process.env.API_KEY;7const BASE_URL = process.env.BASE_URL || "https://api.example.com";89if (!API_KEY) {10 console.error("FATAL: API_KEY environment variable is required.");11 console.error("Configure it in your MCP host config:");12 console.error('{"env": {"API_KEY": "your-key-here"}}');13 process.exit(1);14}1516console.error(`Server starting with API base: ${BASE_URL}`);17console.error(`API_KEY: ${API_KEY.substring(0, 8)}...`);1819const server = new McpServer({20 name: "auth-demo-server",21 version: "1.0.0",22});2324server.tool(25 "fetch-data",26 "Fetch data from the authenticated API",27 { endpoint: z.string() },28 async ({ endpoint }) => {29 const response = await fetch(`${BASE_URL}/${endpoint}`, {30 headers: {31 Authorization: `Bearer ${API_KEY}`,32 "Content-Type": "application/json",33 },34 });3536 if (response.status === 401) {37 return {38 content: [{39 type: "text",40 text: "Authentication failed. Your API key may be expired. Generate a new key and update your MCP host config.",41 }],42 isError: true,43 };44 }4546 if (!response.ok) {47 return {48 content: [{49 type: "text",50 text: `API error: ${response.status} ${response.statusText}`,51 }],52 isError: true,53 };54 }5556 const data = await response.json();57 return {58 content: [{ type: "text", text: JSON.stringify(data, null, 2) }],59 };60 }61);6263const transport = new StdioServerTransport();64await server.connect(transport);Common mistakes when fixing MCP authentication and permission errors
Why it's a problem: Using the wrong environment variable name (e.g., GITHUB_TOKEN instead of GITHUB_PERSONAL_ACCESS_TOKEN)
How to avoid: Check the server's documentation for the exact variable name. Names are case-sensitive and must match precisely.
Why it's a problem: Pasting the API key with leading/trailing whitespace
How to avoid: Trim any whitespace from the API key value. Some editors add invisible characters when pasting.
Why it's a problem: Using an expired or revoked API key
How to avoid: Test the key independently with curl. If it fails, generate a new one from the service's dashboard.
Why it's a problem: Creating a GitHub token without the required scopes
How to avoid: The MCP GitHub server needs at least repo and read:org scopes. Check the error message for the specific scopes required.
Best practices
- Always validate credentials at server startup with clear error messages
- Test API keys independently before adding them to MCP config
- Use the minimum required permissions/scopes for API tokens
- Rotate API keys periodically and update your MCP config when you do
- Never log full API keys — log only the first few characters for identification
- Document which environment variables your server requires in its README
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
My MCP server is showing '[error message]' when trying to connect to [service]. I configured the API key in my [Claude Desktop / Cursor] config. Help me debug the authentication error and fix it.
My MCP GitHub server shows '401 Unauthorized'. I set GITHUB_PERSONAL_ACCESS_TOKEN in my config. Help me verify the token is valid, has the right scopes, and is configured correctly.
Frequently asked questions
Where should I store API keys for MCP servers?
In the env block of your MCP host configuration file (claude_desktop_config.json or .cursor/mcp.json). Never hardcode keys in server source code or commit them to version control.
My API key works in curl but not in the MCP server. Why?
The key might not be reaching the server process. Add a startup log (console.error) that prints whether the env var is set (without printing the actual value). Also check for whitespace or invisible characters in the config file.
How do I rotate API keys without downtime?
Generate the new key first, update the env block in your MCP config, then restart the MCP host. Finally, revoke the old key. The downtime is only the few seconds it takes to restart.
Can MCP servers use OAuth instead of API keys?
Yes, remote MCP servers using Streamable HTTP transport support OAuth 2.1 authentication. This is defined in the MCP specification and is the recommended auth method for shared remote servers.
What if the server needs multiple API keys for different services?
Add all required keys to the env block. Each key is a separate key-value pair. The server accesses them independently via process.env.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation