Tutorial: Who am I?
This tutorial will guide you through the process of setting up MCP Auth to authenticate users and retrieve their identity information from the authorization server.
After completing this tutorial, you will have:
- ✅ A basic understanding of how to use MCP Auth to authenticate users.
- ✅ A MCP server that offers a tool to retrieve user identity information.
Overview
The tutorial will involve the following components:
- MCP server: A simple MCP server that uses MCP official SDKs to handle requests.
- MCP inspector: A visual testing tool for MCP servers. It also acts as an OAuth / OIDC client to initiate the authorization flow and retrieve access tokens.
- Authorization server: An OAuth 2.1 or OpenID Connect provider that manages user identities and issues access tokens.
Here's a high-level diagram of the interaction between these components:
Understand your authorization server
Retrieving user identity information
To complete this tutorial, your authorization server should offer an API to retrieve user identity information:
- Logto
- Keycloak
- OIDC
- OAuth 2
Logto is an OpenID Connect provider that supports the standard userinfo endpoint to retrieve user identity information.
To fetch an access token that can be used to access the userinfo endpoint, at least two scopes are required: openid
and profile
. You can continue reading as we'll cover the scope configuration later.
Keycloak is an open-source identity and access management solution that supports multiple protocols, including OpenID Connect (OIDC). As an OIDC provider, it implements the standard userinfo endpoint to retrieve user identity information.
To fetch an access token that can be used to access the userinfo endpoint, at least two scopes are required: openid
and profile
. You can continue reading as we'll cover the scope configuration later.
Most OpenID Connect providers support the userinfo endpoint to retrieve user identity information.
Check your provider's documentation to see if it supports this endpoint. If your provider supports OpenID Connect Discovery, you can also check if the userinfo_endpoint
is included in the discovery document (response from the .well-known/openid-configuration
endpoint).
To fetch an access token that can be used to access the userinfo endpoint, at least two scopes are required: openid
and profile
. Check your provider's documentation to see the mapping of scopes to user identity claims.
While OAuth 2.0 does not define a standard way to retrieve user identity information, many providers implement their own endpoints to do so. Check your provider's documentation to see how to retrieve user identity information using an access token and what parameters are required to fetch such access token when invoking the authorization flow.
Dynamic Client Registration
Dynamic Client Registration is not required for this tutorial, but it can be useful if you want to automate the MCP client registration process with your authorization server. Check Is Dynamic Client Registration required? for more details.
Set up the MCP server
We will use the MCP official SDKs to create a MCP server with a whoami
tool that retrieves user identity information from the authorization server.
Create a new project
- Python
- Node.js
mkdir mcp-server
cd mcp-server
uv init # Or use `pipenv` or `poetry` to create a new virtual environment
Set up a new Node.js project:
mkdir mcp-server
cd mcp-server
npm init -y # Or use `pnpm init`
npm pkg set type="module"
npm pkg set main="whoami.js"
npm pkg set scripts.start="node whoami.js"
Install the MCP SDK and dependencies
- Python
- Node.js
pip install "mcp[cli]" starlette uvicorn
Or any other package manager you prefer, such as uv
or poetry
.
npm install @modelcontextprotocol/sdk express
Or any other package manager you prefer, such as pnpm
or yarn
.
Create the MCP server
First, let's create an MCP server that implements a whoami
tool.
- Python
- Node.js
Create a file named whoami.py
and add the following code:
from mcp.server.fastmcp import FastMCP
from starlette.applications import Starlette
from starlette.routing import Mount
mcp = FastMCP("WhoAmI")
@mcp.tool()
def whoami() -> dict[str, Any]:
"""A tool that returns the current user's information."""
return {"error": "Not authenticated"}
app = Starlette(
routes=[Mount('/', app=mcp.sse_app())]
)
Run the server with:
uvicorn whoami:app --host 0.0.0.0 --port 3001
Since the current MCP inspector implementation does not handle authorization flows, we will use the SSE approach to set up the MCP server. We'll update the code here once the MCP inspector supports authorization flows.
You can also use pnpm
or yarn
if you prefer.
Create a file named whoami.js
and add the following code:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import express from 'express';
// Create an MCP server
const server = new McpServer({
name: 'WhoAmI',
version: '0.0.0',
});
// Add a tool to the server that returns the current user's information
server.tool('whoami', async () => {
return {
content: [{ type: 'text', text: JSON.stringify({ error: 'Not authenticated' }) }],
};
});
// Below is the boilerplate code from MCP SDK documentation
const PORT = 3001;
const app = express();
const transports = {};
app.get('/sse', async (_req, res) => {
const transport = new SSEServerTransport('/messages', res);
transports[transport.sessionId] = transport;
res.on('close', () => {
delete transports[transport.sessionId];
});
await server.connect(transport);
});
app.post('/messages', async (req, res) => {
const sessionId = String(req.query.sessionId);
const transport = transports[sessionId];
if (transport) {
await transport.handlePostMessage(req, res, req.body);
} else {
res.status(400).send('No transport found for sessionId');
}
});
app.listen(PORT);
Run the server with:
npm start
Inspect the MCP server
Clone and run MCP inspector
Now that we have the MCP server running, we can use the MCP inspector to see if the whoami
tool is available.
Due to the limit of the current implementation, we've forked the MCP inspector to make it more flexible and scalable for authentication and authorization. We've also submitted a pull request to the original repository to include our changes.
To run the MCP inspector, you can use the following command (Node.js is required):
git clone https://github.com/mcp-auth/inspector.git
cd inspector
npm install
npm run dev
Then, open your browser and navigate to http://localhost:6274/
(or other URL shown in the terminal) to access the MCP inspector.
Connect MCP inspector to the MCP server
Before we proceed, check the following configuration in MCP inspector:
- Transport Type: Set to
SSE
. - URL: Set to the URL of your MCP server. In our case, it should be
http://localhost:3001/sse
.
Now you can click the "Connect" button to see if the MCP inspector can connect to the MCP server. If everything is okay, you should see the "Connected" status in the MCP inspector.
Checkpoint: Run the whoami
tool
- In the top menu of the MCP inspector, click on the "Tools" tab.
- Click on the "List Tools" button.
- You should see the
whoami
tool listed on the page. Click on it to open the tool details. - You should see the "Run Tool" button in the right side. Click on it to run the tool.
- You should see the tool result with the JSON response
{"error": "Not authenticated"}
.
Integrate with your authorization server
To complete this section, there are several considerations to take into account:
The issuer URL of your authorization server
This is usually the base URL of your authorization server, such as https://auth.example.com
. Some providers may have a path like https://example.logto.app/oidc
, so make sure to check your provider's documentation.
How to retrieve the authorization server metadata
- If your authorization server conforms to the OAuth 2.0 Authorization Server Metadata or OpenID Connect Discovery, you can use the MCP Auth built-in utilities to fetch the metadata automatically.
- If your authorization server does not conform to these standards, you will need to manually specify the metadata URL or endpoints in the MCP server configuration. Check your provider's documentation for the specific endpoints.
How to register the MCP inspector as a client in your authorization server
- If your authorization server supports Dynamic Client Registration, you can skip this step as the MCP inspector will automatically register itself as a client.
- If your authorization server does not support Dynamic Client Registration, you will need to manually register the MCP inspector as a client in your authorization server.
How to retrieve user identity information and how to configure the authorization request parameters
-
For OpenID Connect providers: usually you need to request at least the
openid
andprofile
scopes when initiating the authorization flow. This will ensure that the access token returned by the authorization server contains the necessary scopes to access the userinfo endpoint to retrieve user identity information.Note: Some of the providers may not support the userinfo endpoint.
-
For OAuth 2.0 / OAuth 2.1 providers: check your provider's documentation to see how to retrieve user identity information using an access token and what parameters are required to fetch such access token when invoking the authorization flow.
While each provider may have its own specific requirements, the following steps will guide you through the process of integrating the MCP inspector and MCP server with provider-specific configurations.
Register MCP inspector as a client
- Logto
- Keycloak
- OIDC
- OAuth 2
Integrating with Logto is straightforward as it's an OpenID Connect provider that supports the standard userinfo endpoint to retrieve user identity information.
Since Logto does not support Dynamic Client Registration yet, you will need to manually register the MCP inspector as a client in your Logto tenant:
- Open your MCP inspector, click on the "OAuth Configuration" button. Copy the Redirect URL (auto-populated) value, which should be something like
http://localhost:6274/oauth/callback
. - Sign in to Logto Console (or your self-hosted Logto Console).
- Navigate to the "Applications" tab, click on "Create application". In the bottom of the page, click on "Create app without framework".
- Fill in the application details, then click on "Create application":
- Select an application type: Choose "Single-page application".
- Application name: Enter a name for your application, e.g., "MCP Inspector".
- In the "Settings / Redirect URIs" section, paste the Redirect URL (auto-populated) value you copied from the MCP inspector. Then click on "Save changes" in the bottom bar.
- In the top card, you will see the "App ID" value. Copy it.
- Go back to the MCP inspector and paste the "App ID" value in the "OAuth Configuration" section under "Client ID".
- Enter the value
{"scope": "openid profile email"}
in the "Auth Params" field. This will ensure that the access token returned by Logto contains the necessary scopes to access the userinfo endpoint.
Keycloak is an open-source identity and access management solution that supports OpenID Connect protocol.
While Keycloak supports dynamic client registration, its client registration endpoint does not support CORS, preventing most MCP clients from registering directly. Therefore, we'll need to manually register our client.
Although Keycloak can be installed in various ways (bare metal, kubernetes, etc.), for this tutorial, we'll use Docker for a quick and straightforward setup.
Let's set up a Keycloak instance and configure it for our needs:
- First, run a Keycloak instance using Docker following the official documentation:
docker run -p 8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:26.2.4 start-dev
-
Access the Keycloak Admin Console (http://localhost:8080/admin) and log in with these credentials:
- Username:
admin
- Password:
admin
- Username:
-
Create a new Realm:
- Click "Create Realm" in the top-left corner
- Enter
mcp-realm
in the "Realm name" field - Click "Create"
-
Create a test user:
- Click "Users" in the left menu
- Click "Create new user"
- Fill in the user details:
- Username:
testuser
- First name and Last name can be any values
- Username:
- Click "Create"
- In the "Credentials" tab, set a password and uncheck "Temporary"
-
Register MCP Inspector as a client:
- Open your MCP inspector, click on the "OAuth Configuration" button. Copy the Redirect URL (auto-populated) value, which should be something like
http://localhost:6274/oauth/callback
. - In the Keycloak Admin Console, click "Clients" in the left menu
- Click "Create client"
- Fill in the client details:
- Client type: Select "OpenID Connect"
- Client ID: Enter
mcp-inspector
- Click "Next"
- On the "Capability config" page:
- Ensure "Standard flow" is enabled
- Click "Next"
- On the "Login settings" page:
- Paste the previously copied MCP Inspector callback URL into "Valid redirect URIs"
- Enter
http://localhost:6274
in "Web origins" - Click "Save"
- Copy the "Client ID" (which is
mcp-inspector
)
- Open your MCP inspector, click on the "OAuth Configuration" button. Copy the Redirect URL (auto-populated) value, which should be something like
-
Back in the MCP Inspector:
- Paste the copied Client ID into the "Client ID" field in the "OAuth Configuration" section
- Enter the following value in the "Auth Params" field to request the necessary scopes:
{ "scope": "openid profile email" }
This is a generic OpenID Connect provider integration guide. Check your provider's documentation for specific details.
If your OpenID Connect provider supports Dynamic Client Registration, you can directly go to step 8 below to configure the MCP inspector; otherwise, you will need to manually register the MCP inspector as a client in your OpenID Connect provider:
- Open your MCP inspector, click on the "OAuth Configuration" button. Copy the Redirect URL (auto-populated) value, which should be something like
http://localhost:6274/oauth/callback
. - Sign in to your OpenID Connect provider's console.
- Navigate to the "Applications" or "Clients" section, then create a new application or client.
- If your provider requires a client type, select "Single-page application" or "Public client".
- After creating the application, you will need to configure the redirect URI. Paste the Redirect URL (auto-populated) value you copied from the MCP inspector.
- Find the "Client ID" or "Application ID" of the newly created application and copy it.
- Go back to the MCP inspector and paste the "Client ID" value in the "OAuth Configuration" section under "Client ID".
- For standard OpenID Connect providers, you can enter the following value in the "Auth Params" field to request the necessary scopes to access the userinfo endpoint:
{ "scope": "openid profile email" }
This is a generic OAuth 2.0 / OAuth 2.1 provider integration guide. Check your provider's documentation for specific details.
If your OAuth 2.0 / OAuth 2.1 provider supports Dynamic Client Registration, you can directly go to step 8 below to configure the MCP inspector; otherwise, you will need to manually register the MCP inspector as a client in your OAuth 2.0 / OAuth 2.1 provider:
- Open your MCP inspector, click on the "OAuth Configuration" button. Copy the Redirect URL (auto-populated) value, which should be something like
http://localhost:6274/oauth/callback
. - Sign in to your OAuth 2.0 / OAuth 2.1 provider's console.
- Navigate to the "Applications" or "Clients" section, then create a new application or client.
- If your provider requires a client type, select "Single-page application" or "Public client".
- After creating the application, you will need to configure the redirect URI. Paste the Redirect URL (auto-populated) value you copied from the MCP inspector.
- Find the "Client ID" or "Application ID" of the newly created application and copy it.
- Go back to the MCP inspector and paste the "Client ID" value in the "OAuth Configuration" section under "Client ID".
- Read your provider's documentation to see how to retrieve access tokens for user identity information. You may need to specify the scopes or parameters required to fetch the access token. For example, if your provider requires the
profile
scope to access user identity information, you can enter the following value in the "Auth Params" field:
{ "scope": "profile" }
Set up MCP auth
In your MCP server project, you need to install the MCP Auth SDK and configure it to use your authorization server metadata.
- Python
- Node.js
First, install the mcpauth
package:
pip install mcpauth
Or any other package manager you prefer, such as uv
or poetry
.
First, install the mcp-auth
package:
npm install mcp-auth
MCP Auth requires the authorization server metadata to be able to initialize. Depending on your provider:
- Logto
- Keycloak
- OIDC
- OAuth 2
The issuer URL can be found in your application details page in Logto Console, in the "Endpoints & Credentials / Issuer endpoint" section. It should look like https://my-project.logto.app/oidc
.
- Python
- Node.js
Update the whoami.py
to include the MCP Auth configuration:
from mcpauth import MCPAuth
from mcpauth.config import AuthServerType
from mcpauth.utils import fetch_server_config
auth_issuer = '<issuer-endpoint>' # Replace with your issuer endpoint
auth_server_config = fetch_server_config(auth_issuer, type=AuthServerType.OIDC)
mcp_auth = MCPAuth(server=auth_server_config)
Update the whoami.js
to include the MCP Auth configuration:
import { MCPAuth, fetchServerConfig } from 'mcp-auth';
const authIssuer = '<issuer-endpoint>'; // Replace with your issuer endpoint
const mcpAuth = new MCPAuth({
server: await fetchServerConfig(authIssuer, { type: 'oidc' }),
});
Now, we need to create a custom access token verifier that will fetch the user identity information from the authorization server using the access token provided by the MCP inspector.
- Python
- Node.js
import pydantic
import requests
from mcpauth.exceptions import (
MCPAuthTokenVerificationException,
MCPAuthTokenVerificationExceptionCode,
)
from mcpauth.types import AuthInfo
def verify_access_token(token: str) -> AuthInfo:
"""
Verifies the provided Bearer token by fetching user information from the authorization server.
If the token is valid, it returns an `AuthInfo` object containing the user's information.
:param token: The Bearer token to received from the MCP inspector.
"""
issuer = auth_server_config.metadata.issuer
endpoint = auth_server_config.metadata.userinfo_endpoint # The provider should support the userinfo endpoint
if not endpoint:
raise ValueError(
"Userinfo endpoint is not configured in the auth server metadata."
)
try:
response = requests.get(
endpoint,
headers={"Authorization": f"Bearer {token}"}, # Standard Bearer token header
)
response.raise_for_status() # Ensure we raise an error for HTTP errors
json = response.json() # Parse the JSON response
return AuthInfo(
token=token,
subject=json.get("sub"), # 'sub' is a standard claim for the subject (user's ID)
issuer=issuer, # Use the issuer from the metadata
claims=json, # Include all claims (JSON fields) returned by the userinfo endpoint
)
# `AuthInfo` is a Pydantic model, so validation errors usually mean the response didn't match
# the expected structure
except pydantic.ValidationError as e:
raise MCPAuthTokenVerificationException(
MCPAuthTokenVerificationExceptionCode.INVALID_TOKEN,
cause=e,
)
# Handle other exceptions that may occur during the request
except Exception as e:
raise MCPAuthTokenVerificationException(
MCPAuthTokenVerificationExceptionCode.TOKEN_VERIFICATION_FAILED,
cause=e,
)
import { MCPAuthTokenVerificationError } from 'mcp-auth';
/**
* Verifies the provided Bearer token by fetching user information from the authorization server.
* If the token is valid, it returns an `AuthInfo` object containing the user's information.
*/
const verifyToken = async (token) => {
const { issuer, userinfoEndpoint } = mcpAuth.config.server.metadata;
if (!userinfoEndpoint) {
throw new Error('Userinfo endpoint is not configured in the server metadata');
}
const response = await fetch(userinfoEndpoint, {
headers: { Authorization: `Bearer ${token}` },
});
if (!response.ok) {
throw new MCPAuthTokenVerificationError('token_verification_failed', response);
}
const userInfo = await response.json();
if (typeof userInfo !== 'object' || userInfo === null || !('sub' in userInfo)) {
throw new MCPAuthTokenVerificationError('invalid_token', response);
}
return {
token,
issuer,
subject: String(userInfo.sub), // 'sub' is a standard claim for the subject (user's ID)
clientId: '', // Client ID is not used in this example, but can be set if needed
scopes: [],
claims: userInfo,
};
};
The issuer URL can be found in your Keycloak Admin Console. In your 'mcp-realm', navigate to "Realm settings / Endpoints" section and click on "OpenID Endpoint Configuration" link. The issuer
field in the JSON document will contain your issuer URL, which should look like http://localhost:8080/realms/mcp-realm
.
- Python
- Node.js
Update the whoami.py
to include the MCP Auth configuration:
from mcpauth import MCPAuth
from mcpauth.config import AuthServerType
from mcpauth.utils import fetch_server_config
auth_issuer = '<issuer-endpoint>' # Replace with your issuer endpoint
auth_server_config = fetch_server_config(auth_issuer, type=AuthServerType.OIDC)
mcp_auth = MCPAuth(server=auth_server_config)
Update the whoami.js
to include the MCP Auth configuration:
import { MCPAuth, fetchServerConfig } from 'mcp-auth';
const authIssuer = '<issuer-endpoint>'; // Replace with your issuer endpoint
const mcpAuth = new MCPAuth({
server: await fetchServerConfig(authIssuer, { type: 'oidc' }),
});
Now, we need to create a custom access token verifier that will fetch the user identity information from the authorization server using the access token provided by the MCP inspector.
- Python
- Node.js
import pydantic
import requests
from mcpauth.exceptions import (
MCPAuthTokenVerificationException,
MCPAuthTokenVerificationExceptionCode,
)
from mcpauth.types import AuthInfo
def verify_access_token(token: str) -> AuthInfo:
"""
Verifies the provided Bearer token by fetching user information from the authorization server.
If the token is valid, it returns an `AuthInfo` object containing the user's information.
:param token: The Bearer token to received from the MCP inspector.
"""
issuer = auth_server_config.metadata.issuer
endpoint = auth_server_config.metadata.userinfo_endpoint # The provider should support the userinfo endpoint
if not endpoint:
raise ValueError(
"Userinfo endpoint is not configured in the auth server metadata."
)
try:
response = requests.get(
endpoint,
headers={"Authorization": f"Bearer {token}"}, # Standard Bearer token header
)
response.raise_for_status() # Ensure we raise an error for HTTP errors
json = response.json() # Parse the JSON response
return AuthInfo(
token=token,
subject=json.get("sub"), # 'sub' is a standard claim for the subject (user's ID)
issuer=issuer, # Use the issuer from the metadata
claims=json, # Include all claims (JSON fields) returned by the userinfo endpoint
)
# `AuthInfo` is a Pydantic model, so validation errors usually mean the response didn't match
# the expected structure
except pydantic.ValidationError as e:
raise MCPAuthTokenVerificationException(
MCPAuthTokenVerificationExceptionCode.INVALID_TOKEN,
cause=e,
)
# Handle other exceptions that may occur during the request
except Exception as e:
raise MCPAuthTokenVerificationException(
MCPAuthTokenVerificationExceptionCode.TOKEN_VERIFICATION_FAILED,
cause=e,
)
import { MCPAuthTokenVerificationError } from 'mcp-auth';
/**
* Verifies the provided Bearer token by fetching user information from the authorization server.
* If the token is valid, it returns an `AuthInfo` object containing the user's information.
*/
const verifyToken = async (token) => {
const { issuer, userinfoEndpoint } = mcpAuth.config.server.metadata;
if (!userinfoEndpoint) {
throw new Error('Userinfo endpoint is not configured in the server metadata');
}
const response = await fetch(userinfoEndpoint, {
headers: { Authorization: `Bearer ${token}` },
});
if (!response.ok) {
throw new MCPAuthTokenVerificationError('token_verification_failed', response);
}
const userInfo = await response.json();
if (typeof userInfo !== 'object' || userInfo === null || !('sub' in userInfo)) {
throw new MCPAuthTokenVerificationError('invalid_token', response);
}
return {
token,
issuer,
subject: String(userInfo.sub), // 'sub' is a standard claim for the subject (user's ID)
clientId: '', // Client ID is not used in this example, but can be set if needed
scopes: [],
claims: userInfo,
};
};
The following code also assumes that the authorization server supports the userinfo endpoint to retrieve user identity information. If your provider does not support this endpoint, you will need to check your provider's documentation for the specific endpoint and replace the userinfo endpoint variable with the correct URL.
- Python
- Node.js
Update the whoami.py
to include the MCP Auth configuration:
from mcpauth import MCPAuth
from mcpauth.config import AuthServerType
from mcpauth.utils import fetch_server_config
auth_issuer = '<issuer-endpoint>' # Replace with your issuer endpoint
auth_server_config = fetch_server_config(auth_issuer, type=AuthServerType.OIDC)
mcp_auth = MCPAuth(server=auth_server_config)
Update the whoami.js
to include the MCP Auth configuration:
import { MCPAuth, fetchServerConfig } from 'mcp-auth';
const authIssuer = '<issuer-endpoint>'; // Replace with your issuer endpoint
const mcpAuth = new MCPAuth({
server: await fetchServerConfig(authIssuer, { type: 'oidc' }),
});
In some cases, the provider response may be malformed or not conforming to the expected metadata format. If you are confident that the provider is compliant, you can transpile the metadata via the config option:
- Python
- Node.js
mcp_auth = MCPAuth(
server=fetch_server_config(
# ...other options
transpile_data=lambda data: {**data, 'response_types_supported': ['code']}
)
)
const mcpAuth = new MCPAuth({
server: await fetchServerConfig(authIssuer, {
// ...other options
transpileData: (data) => ({ ...data, response_types_supported: ['code'] }),
}),
});
If your provider does not support OpenID Connect Discovery, you can manually specify the metadata URL or endpoints. Check Other ways to initialize MCP Auth for more details.
Now, we need to create a custom access token verifier that will fetch the user identity information from the authorization server using the access token provided by the MCP inspector.
- Python
- Node.js
import pydantic
import requests
from mcpauth.exceptions import (
MCPAuthTokenVerificationException,
MCPAuthTokenVerificationExceptionCode,
)
from mcpauth.types import AuthInfo
def verify_access_token(token: str) -> AuthInfo:
"""
Verifies the provided Bearer token by fetching user information from the authorization server.
If the token is valid, it returns an `AuthInfo` object containing the user's information.
:param token: The Bearer token to received from the MCP inspector.
"""
issuer = auth_server_config.metadata.issuer
endpoint = auth_server_config.metadata.userinfo_endpoint # The provider should support the userinfo endpoint
if not endpoint:
raise ValueError(
"Userinfo endpoint is not configured in the auth server metadata."
)
try:
response = requests.get(
endpoint,
headers={"Authorization": f"Bearer {token}"}, # Standard Bearer token header
)
response.raise_for_status() # Ensure we raise an error for HTTP errors
json = response.json() # Parse the JSON response
return AuthInfo(
token=token,
subject=json.get("sub"), # 'sub' is a standard claim for the subject (user's ID)
issuer=issuer, # Use the issuer from the metadata
claims=json, # Include all claims (JSON fields) returned by the userinfo endpoint
)
# `AuthInfo` is a Pydantic model, so validation errors usually mean the response didn't match
# the expected structure
except pydantic.ValidationError as e:
raise MCPAuthTokenVerificationException(
MCPAuthTokenVerificationExceptionCode.INVALID_TOKEN,
cause=e,
)
# Handle other exceptions that may occur during the request
except Exception as e:
raise MCPAuthTokenVerificationException(
MCPAuthTokenVerificationExceptionCode.TOKEN_VERIFICATION_FAILED,
cause=e,
)
import { MCPAuthTokenVerificationError } from 'mcp-auth';
/**
* Verifies the provided Bearer token by fetching user information from the authorization server.
* If the token is valid, it returns an `AuthInfo` object containing the user's information.
*/
const verifyToken = async (token) => {
const { issuer, userinfoEndpoint } = mcpAuth.config.server.metadata;
if (!userinfoEndpoint) {
throw new Error('Userinfo endpoint is not configured in the server metadata');
}
const response = await fetch(userinfoEndpoint, {
headers: { Authorization: `Bearer ${token}` },
});
if (!response.ok) {
throw new MCPAuthTokenVerificationError('token_verification_failed', response);
}
const userInfo = await response.json();
if (typeof userInfo !== 'object' || userInfo === null || !('sub' in userInfo)) {
throw new MCPAuthTokenVerificationError('invalid_token', response);
}
return {
token,
issuer,
subject: String(userInfo.sub), // 'sub' is a standard claim for the subject (user's ID)
clientId: '', // Client ID is not used in this example, but can be set if needed
scopes: [],
claims: userInfo,
};
};
As we mentioned earlier, OAuth 2.0 does not define a standard way to retrieve user identity information. The following code assumes that your provider has a specific endpoint to retrieve user identity information using an access token. You will need to check your provider's documentation for the specific endpoint and replace the userinfo endpoint variable with the correct URL.
- Python
- Node.js
Update the whoami.py
to include the MCP Auth configuration:
from mcpauth import MCPAuth
from mcpauth.config import AuthServerType
from mcpauth.utils import fetch_server_config
auth_issuer = '<issuer-endpoint>' # Replace with your issuer endpoint
auth_server_config = fetch_server_config(auth_issuer, type=AuthServerType.OAUTH)
mcp_auth = MCPAuth(server=auth_server_config)
Update the whoami.js
to include the MCP Auth configuration:
import { MCPAuth, fetchServerConfig } from 'mcp-auth';
const authIssuer = '<issuer-endpoint>'; // Replace with your issuer endpoint
const mcpAuth = new MCPAuth({
server: await fetchServerConfig(authIssuer, { type: 'oauth' }),
});
In some cases, the provider response may be malformed or not conforming to the expected metadata format. If you are confident that the provider is compliant, you can transpile the metadata via the config option:
- Python
- Node.js
mcp_auth = MCPAuth(
server=fetch_server_config(
# ...other options
transpile_data=lambda data: {**data, 'response_types_supported': ['code']}
)
)
const mcpAuth = new MCPAuth({
server: await fetchServerConfig(authIssuer, {
// ...other options
transpileData: (data) => ({ ...data, response_types_supported: ['code'] }),
}),
});
If your provider does not support OAuth 2.0 Authorization Server Metadata, you can manually specify the metadata URL or endpoints. Check Other ways to initialize MCP Auth for more details.
Now, we need to create a custom access token verifier that will fetch the user identity information from the authorization server using the access token provided by the MCP inspector.
- Python
- Node.js
import pydantic
import requests
from mcpauth.exceptions import (
MCPAuthTokenVerificationException,
MCPAuthTokenVerificationExceptionCode,
)
from mcpauth.types import AuthInfo
def verify_access_token(token: str) -> AuthInfo:
"""
Verifies the provided Bearer token by fetching user information from the authorization server.
If the token is valid, it returns an `AuthInfo` object containing the user's information.
:param token: The Bearer token to received from the MCP inspector.
"""
try:
# The following code assumes your authorization server has an endpoint for fetching user info
# using the access token issued by the authorization flow.
# Adjust the URL and headers as needed based on your provider's API.
response = requests.get(
"https://your-authorization-server.com/userinfo",
headers={"Authorization": f"Bearer {token}"},
)
response.raise_for_status() # Ensure we raise an error for HTTP errors
json = response.json() # Parse the JSON response
# The following code assumes the user info response is an object with a 'sub' field that
# identifies the user. You may need to adjust this based on your provider's API.
return AuthInfo(
token=token,
subject=json.get("sub"),
issuer=auth_issuer, # Use the configured issuer
claims=json, # Include all claims (JSON fields) returned by the endpoint
)
# `AuthInfo` is a Pydantic model, so validation errors usually mean the response didn't match
# the expected structure
except pydantic.ValidationError as e:
raise MCPAuthTokenVerificationException(
MCPAuthTokenVerificationExceptionCode.INVALID_TOKEN,
cause=e,
)
# Handle other exceptions that may occur during the request
except Exception as e:
raise MCPAuthTokenVerificationException(
MCPAuthTokenVerificationExceptionCode.TOKEN_VERIFICATION_FAILED,
cause=e,
)
import { MCPAuthTokenVerificationError } from 'mcp-auth';
/**
* Verifies the provided Bearer token by fetching user information from the authorization server.
* If the token is valid, it returns an `AuthInfo` object containing the user's information.
*/
const verifyToken = async (token) => {
// The following code assumes your authorization server has an endpoint for fetching user info
// using the access token issued by the authorization flow.
// Adjust the URL and headers as needed based on your provider's API.
const response = await fetch('https://your-authorization-server.com/userinfo', {
headers: { Authorization: `Bearer ${token}` },
});
if (!response.ok) {
throw new MCPAuthTokenVerificationError('token_verification_failed', response);
}
const userInfo = await response.json();
// The following code assumes the user info response is an object with a 'sub' field that
// identifies the user. You may need to adjust this based on your provider's API.
if (typeof userInfo !== 'object' || userInfo === null || !('sub' in userInfo)) {
throw new MCPAuthTokenVerificationError('invalid_token', response);
}
return {
token,
issuer: authIssuer,
subject: String(userInfo.sub), // Adjust this based on your provider's user ID field
clientId: '', // Client ID is not used in this example, but can be set if needed
scopes: [],
claims: userInfo,
};
};
Update MCP server
We are almost done! It's time to update the MCP server to apply the MCP Auth route and middleware function, then make the whoami
tool return the actual user identity information.
- Python
- Node.js
@mcp.tool()
def whoami() -> dict[str, Any]:
"""A tool that returns the current user's information."""
return (
mcp_auth.auth_info.claims
if mcp_auth.auth_info # This will be populated by the Bearer auth middleware
else {"error": "Not authenticated"}
)
# ...
bearer_auth = Middleware(mcp_auth.bearer_auth_middleware(verify_access_token))
app = Starlette(
routes=[
# Add the metadata route (`/.well-known/oauth-authorization-server`)
mcp_auth.metadata_route(),
# Protect the MCP server with the Bearer auth middleware
Mount('/', app=mcp.sse_app(), middleware=[bearer_auth]),
],
)
server.tool('whoami', ({ authInfo }) => {
return {
content: [
{ type: 'text', text: JSON.stringify(authInfo?.claims ?? { error: 'Not authenticated' }) },
],
};
});
// ...
app.use(mcpAuth.delegatedRouter());
app.use(mcpAuth.bearerAuth(verifyToken));
Checkpoint: Run the whoami
tool with authentication
Restart your MCP server and open the MCP inspector in your browser. When you click the "Connect" button, you should be redirected to your authorization server's sign-in page.
Once you sign in and back to the MCP inspector, repeat the actions we did in the previous checkpoint to run the whoami
tool. This time, you should see the user identity information returned by the authorization server.
- Python
- Node.js
Check out the MCP Auth Python SDK repository for the complete code of the MCP server (OIDC version).
Check out the MCP Auth Node.js SDK repository for the complete code of the MCP server (OIDC version). This directory contains both TypeScript and JavaScript versions of the code.
Closing notes
🎊 Congratulations! You have successfully completed the tutorial. Let's recap what we've done:
- Setting up a basic MCP server with the
whoami
tool - Integrating the MCP server with an authorization server using MCP Auth
- Configuring the MCP Inspector to authenticate users and retrieve their identity information
You may also want to explore some advanced topics, including:
- Using JWT (JSON Web Token) for authentication and authorization
- Leveraging resource indicators (RFC 8707) to specify the resources being accessed
- Implementing custom access control mechanisms, such as role-based access control (RBAC) or attribute-based access control (ABAC)
Be sure to check out other tutorials and documentation to make the most of MCP Auth.