教程:我是谁? (Tutorial: Who am I?)
本教程将引导你完成设置 MCP Auth 以认证 (Authentication) 用户并从授权 (Authorization) 服务器获取其身份信息的过程。
完成本教程后,你将获得:
- ✅ 对如何使用 MCP Auth 进行用户认证 (Authentication) 的基本理解。
- ✅ 一个 MCP 服务器,提供用于获取用户身份信息的工具。
概览 (Overview)
本教程将涉及以下组件:
- MCP 服务器:一个简单的 MCP 服务器,使用 MCP 官方 SDK 处理请求。
- MCP inspector:MCP 服务器的可视化测试工具。它还充当 OAuth / OIDC 客户端,发起授权 (Authorization) 流程并获取访问令牌 (Access token)。
- 授权 (Authorization) 服务器:一个 OAuth 2.1 或 OpenID Connect 提供商,管理用户身份并颁发访问令牌 (Access token)。
以下是这些组件之间交互的高级流程图:
了解你的授权 (Authorization) 服务器
获取用户身份信息 (Retrieving user identity information)
要完成本教程,你的授权 (Authorization) 服务器应提供用于获取用户身份信息的 API:
- Logto
- Keycloak
- OIDC
- OAuth 2
Logto 是一个 OpenID Connect 提供商,支持标准的 userinfo 端点 用于获取用户身份信息。
要获取可用于访问 userinfo 端点的访问令牌 (Access token),至少需要两个权限 (Scopes):openid
和 profile
。你可以继续阅读,后续会介绍权限 (Scope) 配置。
Keycloak 是一个开源身份和访问管理解决方案,支持多种协议,包括 OpenID Connect (OIDC)。作为 OIDC 提供商,它实现了标准的 userinfo 端点 用于获取用户身份信息。
要获取可用于访问 userinfo 端点的访问令牌 (Access token),至少需要两个权限 (Scopes):openid
和 profile
。你可以继续阅读,后续会介绍权限 (Scope) 配置。
大多数 OpenID Connect 提供商都支持 userinfo 端点 用于获取用户身份信息。
请查阅你的提供商文档,确认是否支持该端点。如果你的提供商支持 OpenID Connect Discovery,你也可以检查 discovery 文档(.well-known/openid-configuration
端点的响应)中是否包含 userinfo_endpoint
。
要获取可用于访问 userinfo 端点的访问令牌 (Access token),至少需要两个权限 (Scopes):openid
和 profile
。请查阅你的提供商文档,了解权限 (Scopes) 与用户身份声明 (Claims) 的映射关系。
虽然 OAuth 2.0 没有定义获取用户身份信息的标准方式,但许多提供商实现了自己的端点。请查阅你的提供商文档,了解如何使用访问令牌 (Access token) 获取用户身份信息,以及在发起授权 (Authorization) 流程时获取该访问令牌 (Access token) 所需的参数。
动态客户端注册 (Dynamic Client Registration)
本教程不要求动态客户端注册,但如果你希望自动化 MCP 客户端在授权 (Authorization) 服务器的注册流程,它会很有用。详见 是否需要动态客户端注册?。
搭建 MCP 服务器
我们将使用 MCP 官方 SDK 创建一个带有 whoami
工具的 MCP 服务器,用于从授权 (Authorization) 服务器获取用户身份信息。
创建新项目 (Create a new project)
- Python
- Node.js
mkdir mcp-server
cd mcp-server
uv init # 或使用 `pipenv` 或 `poetry` 创建新虚拟环境
创建一个新的 Node.js 项目:
mkdir mcp-server
cd mcp-server
npm init -y # 或使用 `pnpm init`
npm pkg set type="module"
npm pkg set main="whoami.js"
npm pkg set scripts.start="node whoami.js"
安装 MCP SDK 及依赖 (Install the MCP SDK and dependencies)
- Python
- Node.js
pip install "mcp[cli]" starlette uvicorn
或使用你喜欢的其他包管理器,如 uv
或 poetry
。
npm install @modelcontextprotocol/sdk express
或使用你喜欢的其他包管理器,如 pnpm
或 yarn
。
创建 MCP 服务器 (Create the MCP server)
首先,让我们创建一个实现 whoami
工具的 MCP 服务器。
- Python
- Node.js
创建名为 whoami.py
的文件,并添加如下代码:
from mcp.server.fastmcp import FastMCP
from starlette.applications import Starlette
from starlette.routing import Mount
from typing import Any
mcp = FastMCP("WhoAmI")
@mcp.tool()
def whoami() -> dict[str, Any]:
"""返回当前用户信息的工具。"""
return {"error": "Not authenticated"}
app = Starlette(
routes=[Mount('/', app=mcp.sse_app())]
)
使用以下命令运行服务器:
uvicorn whoami:app --host 0.0.0.0 --port 3001
由于当前 MCP inspector 实现尚未处理授权 (Authorization) 流程,我们将使用 SSE 方式搭建 MCP 服务器。待 MCP inspector 支持授权 (Authorization) 流程后,我们会更新此处代码。
你也可以使用 pnpm
或 yarn
。
创建名为 whoami.js
的文件,并添加如下代码:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import express from 'express';
// 创建 MCP 服务器
const server = new McpServer({
name: 'WhoAmI',
version: '0.0.0',
});
// 添加一个返回当前用户信息的工具
server.tool('whoami', async () => {
return {
content: [{ type: 'text', text: JSON.stringify({ error: 'Not authenticated' }) }],
};
});
// 以下为 MCP SDK 文档中的样板代码
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);
使用以下命令运行服务器:
npm start
检查 MCP 服务器 (Inspect the MCP server)
克隆并运行 MCP inspector (Clone and run MCP inspector)
现在 MCP 服务器已运行,我们可以使用 MCP inspector 检查 whoami
工具是否可用。
由于当前实现的限制,我们 fork 了 MCP inspector,使其在认证 (Authentication) 和授权 (Authorization) 方面更灵活和可扩展。我们也已向原仓库提交了 pull request。
运行 MCP inspector,可使用以下命令(需要 Node.js):
git clone https://github.com/mcp-auth/inspector.git
cd inspector
npm install
npm run dev
然后,在浏览器中打开 http://localhost:6274/
(或终端显示的其他 URL)访问 MCP inspector。
连接 MCP inspector 到 MCP 服务器 (Connect MCP inspector to the MCP server)
在继续之前,请检查 MCP inspector 中的以下配置:
- Transport Type:设置为
SSE
。 - URL:设置为你的 MCP 服务器的 URL。此处应为
http://localhost:3001/sse
。
现在你可以点击“Connect”按钮,查看 MCP inspector 是否能连接到 MCP 服务器。如果一切正常,你将在 MCP inspector 中看到“Connected”状态。
检查点:运行 whoami
工具 (Checkpoint: Run the whoami
tool)
- 在 MCP inspector 顶部菜单点击 "Tools" 标签页。
- 点击 "List Tools" 按钮。
- 你应该能在页面上看到
whoami
工具,点击它查看工具详情。 - 在右侧你会看到 "Run Tool" 按钮,点击运行该工具。
- 你将看到工具返回的 JSON 响应
{"error": "Not authenticated"}
。
集成你的授权 (Authorization) 服务器 (Integrate with your authorization server)
完成本节内容时,你需要考虑以下事项:
你的授权 (Authorization) 服务器的发行者 (Issuer) URL
通常是你的授权 (Authorization) 服务器的基础 URL,如 https://auth.example.com
。有些提供商可能是类似 https://example.logto.app/oidc
的路径,请查阅你的提供商文档。
如何获取授权 (Authorization) 服务器元数据
- 如果你的授权 (Authorization) 服务器符合 OAuth 2.0 授权服务器元数据 或 OpenID Connect Discovery,你可以使用 MCP Auth 内置工具自动获取元数据。
- 如果不符合这些标准,你需要在 MCP 服务器配置中手动指定元数据 URL 或端点。请查阅你的提供商文档获取具体端点。
如何将 MCP inspector 注册为授权 (Authorization) 服务器的客户端
- 如果你的授权 (Authorization) 服务器支持 动态客户端注册 (Dynamic Client Registration),可以跳过此步骤,MCP inspector 会自动注册为客户端。
- 如果不支持动态客户端注册,你需要手动在授权 (Authorization) 服务器中注册 MCP inspector 为客户端。
如何获取用户身份信息以及如何配置授权 (Authorization) 请求参数
-
对于 OpenID Connect 提供商:通常在发起授权 (Authorization) 流程时需要请求至少
openid
和profile
权限 (Scopes)。这样授权 (Authorization) 服务器返回的访问令牌 (Access token) 就包含访问 userinfo 端点 所需的权限 (Scopes)。注意:部分提供商可能不支持 userinfo 端点。
-
对于 OAuth 2.0 / OAuth 2.1 提供商:请查阅你的提供商文档,了解如何使用访问令牌 (Access token) 获取用户身份信息,以及发起授权 (Authorization) 流程时获取该访问令牌 (Access token) 所需的参数。
虽然每个提供商可能有自己的具体要求,以下步骤将指导你如何结合 MCP inspector 和 MCP 服务器进行针对不同提供商的配置集成。
注册 MCP inspector 为客户端 (Register MCP inspector as a client)
- Logto
- Keycloak
- OIDC
- OAuth 2
与 Logto 集成非常简单,因为它是一个支持标准 userinfo 端点 的 OpenID Connect 提供商。
由于 Logto 目前尚不支持动态客户端注册 (Dynamic Client Registration),你需要手动在 Logto 租户中注册 MCP inspector 为客户端:
- 打开 MCP inspector,点击 "OAuth Configuration" 按钮。复制 Redirect URL (auto-populated),如
http://localhost:6274/oauth/callback
。 - 登录 Logto Console(或你的自托管 Logto Console)。
- 进入 "Applications" 标签页,点击 "Create application"。在页面底部点击 "Create app without framework"。
- 填写应用详情,然后点击 "Create application":
- 选择应用类型:选择 "Single-page application"。
- 应用名称:如 "MCP Inspector"。
- 在 "Settings / Redirect URIs" 区域,粘贴刚才复制的 Redirect URL (auto-populated),然后点击底部栏的 "Save changes"。
- 在顶部卡片中,你会看到 "App ID"。复制它。
- 回到 MCP inspector,在 "OAuth Configuration" 区域的 "Client ID" 字段粘贴 "App ID"。
- 在 "Auth Params" 字段输入
{"scope": "openid profile email"}
,确保 Logto 返回的访问令牌 (Access token) 包含访问 userinfo 端点所需的权限 (Scopes)。
Keycloak 是一个开源身份和访问管理解决方案,支持 OpenID Connect 协议。
虽然 Keycloak 支持动态客户端注册 (Dynamic Client Registration),但其客户端注册端点不支持 CORS,导致大多数 MCP 客户端无法直接注册。因此,我们需要手动注册客户端。
Keycloak 可通过 多种方式 安装(裸机、kubernetes 等),本教程采用 Docker 快速搭建。
让我们搭建 Keycloak 实例并进行配置:
- 按 官方文档 使用 Docker 运行 Keycloak:
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
-
访问 Keycloak 管理控制台 (http://localhost:8080/admin),使用以下凭据登录:
- 用户名:
admin
- 密码:
admin
- 用户名:
-
创建新 Realm:
- 左上角点击 "Create Realm"
- "Realm name" 填写
mcp-realm
- 点击 "Create"
-
创建测试用户:
- 左侧菜单点击 "Users"
- 点击 "Create new user"
- 填写用户信息:
- 用户名:
testuser
- 名字和姓氏可任意填写
- 用户名:
- 点击 "Create"
- 在 "Credentials" 标签页设置密码,并取消勾选 "Temporary"
-
注册 MCP Inspector 为客户端:
- 打开 MCP inspector,点击 "OAuth Configuration" 按钮。复制 Redirect URL (auto-populated),如
http://localhost:6274/oauth/callback
。 - 在 Keycloak 管理控制台左侧点击 "Clients"
- 点击 "Create client"
- 填写客户端信息:
- Client type: 选择 "OpenID Connect"
- Client ID: 输入
mcp-inspector
- 点击 "Next"
- "Capability config" 页面:
- 确保 "Standard flow" 已启用
- 点击 "Next"
- "Login settings" 页面:
- 在 "Valid redirect URIs" 粘贴 MCP Inspector 回调 URL
- "Web origins" 填写
http://localhost:6274
- 点击 "Save"
- 复制 "Client ID"(即
mcp-inspector
)
- 打开 MCP inspector,点击 "OAuth Configuration" 按钮。复制 Redirect URL (auto-populated),如
-
回到 MCP Inspector:
- 在 "OAuth Configuration" 区域的 "Client ID" 字段粘贴复制的 Client ID
- 在 "Auth Params" 字段输入以下内容以请求所需权限 (Scopes):
{ "scope": "openid profile email" }
这是通用 OpenID Connect 提供商集成指南。具体细节请查阅你的提供商文档。
如果你的 OpenID Connect 提供商支持动态客户端注册 (Dynamic Client Registration),可直接跳到第 8 步配置 MCP inspector;否则需手动注册 MCP inspector 为客户端:
- 打开 MCP inspector,点击 "OAuth Configuration" 按钮。复制 Redirect URL (auto-populated),如
http://localhost:6274/oauth/callback
。 - 登录你的 OpenID Connect 提供商控制台。
- 进入 "Applications" 或 "Clients" 区域,创建新应用或客户端。
- 如需选择客户端类型,选择 "Single-page application" 或 "Public client"。
- 创建应用后,需配置重定向 URI。粘贴刚才复制的 Redirect URL (auto-populated)。
- 找到新建应用的 "Client ID" 或 "Application ID",复制它。
- 回到 MCP inspector,在 "OAuth Configuration" 区域的 "Client ID" 字段粘贴该值。
- 对于标准 OpenID Connect 提供商,可在 "Auth Params" 字段输入以下内容以请求访问 userinfo 端点所需权限 (Scopes):
{ "scope": "openid profile email" }
这是通用 OAuth 2.0 / OAuth 2.1 提供商集成指南。具体细节请查阅你的提供商文档。
如果你的 OAuth 2.0 / OAuth 2.1 提供商支持动态客户端注册 (Dynamic Client Registration),可直接跳到第 8 步配置 MCP inspector;否则需手动注册 MCP inspector 为客户端:
- 打开 MCP inspector,点击 "OAuth Configuration" 按钮。复制 Redirect URL (auto-populated),如
http://localhost:6274/oauth/callback
。 - 登录你的 OAuth 2.0 / OAuth 2.1 提供商控制台。
- 进入 "Applications" 或 "Clients" 区域,创建新应用或客户端。
- 如需选择客户端类型,选择 "Single-page application" 或 "Public client"。
- 创建应用后,需配置重定向 URI。粘贴刚才复制的 Redirect URL (auto-populated)。
- 找到新建应用的 "Client ID" 或 "Application ID",复制它。
- 回到 MCP inspector,在 "OAuth Configuration" 区域的 "Client ID" 字段粘贴该值。
- 查阅你的提供商文档,了解如何获取用于用户身份信息的访问令牌 (Access token)。你可能需要指定获取访问令牌 (Access token) 所需的权限 (Scopes) 或参数。例如,如果你的提供商要求
profile
权限 (Scope) 才能访问用户身份信息,可在 "Auth Params" 字段输入:
{ "scope": "profile" }
配置 MCP Auth (Set up MCP auth)
在你的 MCP 服务器项目中,需要安装 MCP Auth SDK 并配置其使用你的授权 (Authorization) 服务器元数据。
- Python
- Node.js
首先,安装 mcpauth
包:
pip install mcpauth
或使用你喜欢的其他包管理器,如 uv
或 poetry
。
首先,安装 mcp-auth
包:
npm install mcp-auth
MCP Auth 需要授权 (Authorization) 服务器元数据以完成初始化。根据你的提供商:
- Logto
- Keycloak
- OIDC
- OAuth 2
发行者 (Issuer) URL 可在 Logto Console 的应用详情页 "Endpoints & Credentials / Issuer endpoint" 区域找到,类似 https://my-project.logto.app/oidc
。
- Python
- Node.js
更新 whoami.py
,加入 MCP Auth 配置:
from mcpauth import MCPAuth
from mcpauth.config import AuthServerType
from mcpauth.utils import fetch_server_config
auth_issuer = '<issuer-endpoint>' # 替换为你的发行者 (Issuer) 端点
auth_server_config = fetch_server_config(auth_issuer, type=AuthServerType.OIDC)
mcp_auth = MCPAuth(server=auth_server_config)
更新 whoami.js
,加入 MCP Auth 配置:
import { MCPAuth, fetchServerConfig } from 'mcp-auth';
const authIssuer = '<issuer-endpoint>'; // 替换为你的发行者 (Issuer) 端点
const mcpAuth = new MCPAuth({
server: await fetchServerConfig(authIssuer, { type: 'oidc' }),
});
现在,我们需要创建一个自定义访问令牌 (Access token) 校验器,用于通过 MCP inspector 提供的访问令牌 (Access token) 从授权服务器获取用户身份信息。
- 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:
"""
通过从授权服务器获取用户信息来校验提供的 Bearer 令牌 (Access token)。
如果令牌有效,则返回包含用户信息的 `AuthInfo` 对象。
:param token: 从 MCP inspector 接收到的 Bearer 令牌 (Access token)。
"""
issuer = auth_server_config.metadata.issuer
endpoint = auth_server_config.metadata.userinfo_endpoint # 提供方应支持 userinfo endpoint
if not endpoint:
raise ValueError(
"Auth server metadata 中未配置 userinfo endpoint。"
)
try:
response = requests.get(
endpoint,
headers={"Authorization": f"Bearer {token}"}, # 标准 Bearer 令牌 (Access token) 头
)
response.raise_for_status() # 确保 HTTP 错误时抛出异常
json = response.json() # 解析 JSON 响应
return AuthInfo(
token=token,
subject=json.get("sub"), # 'sub' 是主体 (Subject)(用户 ID)的标准声明 (Claim)
issuer=issuer, # 使用元数据中的发行者 (Issuer)
claims=json, # 包含 userinfo endpoint 返回的所有声明 (Claims)(JSON 字段)
)
# `AuthInfo` 是 Pydantic 模型,校验错误通常意味着响应结构不匹配预期
except pydantic.ValidationError as e:
raise MCPAuthTokenVerificationException(
MCPAuthTokenVerificationExceptionCode.INVALID_TOKEN,
cause=e,
)
# 处理请求过程中可能发生的其他异常
except Exception as e:
raise MCPAuthTokenVerificationException(
MCPAuthTokenVerificationExceptionCode.TOKEN_VERIFICATION_FAILED,
cause=e,
)
import { MCPAuthTokenVerificationError } from 'mcp-auth';
/**
* 通过从授权服务器获取用户信息来校验提供的 Bearer 令牌 (Access token)。
* 如果令牌有效,则返回包含用户信息的 `AuthInfo` 对象。
*/
const verifyToken = async (token) => {
const { issuer, userinfoEndpoint } = mcpAuth.config.server.metadata;
if (!userinfoEndpoint) {
throw new Error('Server metadata 中未配置 userinfo endpoint');
}
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' 是主体 (Subject)(用户 ID)的标准声明 (Claim)
clientId: '', // 本示例未使用 Client ID,如有需要可设置
scopes: [],
claims: userInfo,
};
};
发行者 (Issuer) URL 可在 Keycloak 管理控制台找到。在你的 'mcp-realm' 下,进入 "Realm settings / Endpoints" 区域,点击 "OpenID Endpoint Configuration" 链接。JSON 文档中的 issuer
字段即为你的发行者 (Issuer) URL,类似 http://localhost:8080/realms/mcp-realm
。
- Python
- Node.js
更新 whoami.py
,加入 MCP Auth 配置:
from mcpauth import MCPAuth
from mcpauth.config import AuthServerType
from mcpauth.utils import fetch_server_config
auth_issuer = '<issuer-endpoint>' # 替换为你的发行者 (Issuer) 端点
auth_server_config = fetch_server_config(auth_issuer, type=AuthServerType.OIDC)
mcp_auth = MCPAuth(server=auth_server_config)
更新 whoami.js
,加入 MCP Auth 配置:
import { MCPAuth, fetchServerConfig } from 'mcp-auth';
const authIssuer = '<issuer-endpoint>'; // 替换为你的发行者 (Issuer) 端点
const mcpAuth = new MCPAuth({
server: await fetchServerConfig(authIssuer, { type: 'oidc' }),
});
现在,我们需要创建一个自定义访问令牌 (Access token) 校验器,用于通过 MCP inspector 提供的访问令牌 (Access token) 从授权服务器获取用户身份信息。
- 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:
"""
通过从授权服务器获取用户信息来校验提供的 Bearer 令牌 (Access token)。
如果令牌有效,则返回包含用户信息的 `AuthInfo` 对象。
:param token: 从 MCP inspector 接收到的 Bearer 令牌 (Access token)。
"""
issuer = auth_server_config.metadata.issuer
endpoint = auth_server_config.metadata.userinfo_endpoint # 提供方应支持 userinfo endpoint
if not endpoint:
raise ValueError(
"Auth server metadata 中未配置 userinfo endpoint。"
)
try:
response = requests.get(
endpoint,
headers={"Authorization": f"Bearer {token}"}, # 标准 Bearer 令牌 (Access token) 头
)
response.raise_for_status() # 确保 HTTP 错误时抛出异常
json = response.json() # 解析 JSON 响应
return AuthInfo(
token=token,
subject=json.get("sub"), # 'sub' 是主体 (Subject)(用户 ID)的标准声明 (Claim)
issuer=issuer, # 使用元数据中的发行者 (Issuer)
claims=json, # 包含 userinfo endpoint 返回的所有声明 (Claims)(JSON 字段)
)
# `AuthInfo` 是 Pydantic 模型,校验错误通常意味着响应结构不匹配预期
except pydantic.ValidationError as e:
raise MCPAuthTokenVerificationException(
MCPAuthTokenVerificationExceptionCode.INVALID_TOKEN,
cause=e,
)
# 处理请求过程中可能发生的其他异常
except Exception as e:
raise MCPAuthTokenVerificationException(
MCPAuthTokenVerificationExceptionCode.TOKEN_VERIFICATION_FAILED,
cause=e,
)
import { MCPAuthTokenVerificationError } from 'mcp-auth';
/**
* 通过从授权服务器获取用户信息来校验提供的 Bearer 令牌 (Access token)。
* 如果令牌有效,则返回包含用户信息的 `AuthInfo` 对象。
*/
const verifyToken = async (token) => {
const { issuer, userinfoEndpoint } = mcpAuth.config.server.metadata;
if (!userinfoEndpoint) {
throw new Error('Server metadata 中未配置 userinfo endpoint');
}
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' 是主体 (Subject)(用户 ID)的标准声明 (Claim)
clientId: '', // 本示例未使用 Client ID,如有需要可设置
scopes: [],
claims: userInfo,
};
};
以下代码同样假设授权 (Authorization) 服务器支持 userinfo 端点 用于获取用户身份信息。如果你的提供商不支持该端点,请查阅文档获取具体端点,并将 userinfo 端点变量替换为正确的 URL。
- Python
- Node.js
更新 whoami.py
,加入 MCP Auth 配置:
from mcpauth import MCPAuth
from mcpauth.config import AuthServerType
from mcpauth.utils import fetch_server_config
auth_issuer = '<issuer-endpoint>' # 替换为你的发行者 (Issuer) 端点
auth_server_config = fetch_server_config(auth_issuer, type=AuthServerType.OIDC)
mcp_auth = MCPAuth(server=auth_server_config)
更新 whoami.js
,加入 MCP Auth 配置:
import { MCPAuth, fetchServerConfig } from 'mcp-auth';
const authIssuer = '<issuer-endpoint>'; // 替换为你的发行者 (Issuer) 端点
const mcpAuth = new MCPAuth({
server: await fetchServerConfig(authIssuer, { type: 'oidc' }),
});
在某些情况下,提供方的响应可能格式错误或不符合预期的元数据格式。如果你确信该提供方是合规的,你可以通过配置选项对元数据进行转译:
- Python
- Node.js
mcp_auth = MCPAuth(
server=fetch_server_config(
# ...其他选项
transpile_data=lambda data: {**data, 'response_types_supported': ['code']}
)
)
const mcpAuth = new MCPAuth({
server: await fetchServerConfig(authIssuer, {
// ...其他选项
transpileData: (data) => ({ ...data, response_types_supported: ['code'] }),
}),
});
如果你的提供商不支持 OpenID Connect Discovery,你可以手动指定元数据 URL 或端点。详见 其他初始化 MCP Auth 的方式。
现在,我们需要创建一个自定义访问令牌 (Access token) 校验器,用于通过 MCP inspector 提供的访问令牌 (Access token) 从授权服务器获取用户身份信息。
- 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:
"""
通过从授权服务器获取用户信息来校验提供的 Bearer 令牌 (Access token)。
如果令牌有效,则返回包含用户信息的 `AuthInfo` 对象。
:param token: 从 MCP inspector 接收到的 Bearer 令牌 (Access token)。
"""
issuer = auth_server_config.metadata.issuer
endpoint = auth_server_config.metadata.userinfo_endpoint # 提供方应支持 userinfo endpoint
if not endpoint:
raise ValueError(
"Auth server metadata 中未配置 userinfo endpoint。"
)
try:
response = requests.get(
endpoint,
headers={"Authorization": f"Bearer {token}"}, # 标准 Bearer 令牌 (Access token) 头
)
response.raise_for_status() # 确保 HTTP 错误时抛出异常
json = response.json() # 解析 JSON 响应
return AuthInfo(
token=token,
subject=json.get("sub"), # 'sub' 是主体 (Subject)(用户 ID)的标准声明 (Claim)
issuer=issuer, # 使用元数据中的发行者 (Issuer)
claims=json, # 包含 userinfo endpoint 返回的所有声明 (Claims)(JSON 字段)
)
# `AuthInfo` 是 Pydantic 模型,校验错误通常意味着响应结构不匹配预期
except pydantic.ValidationError as e:
raise MCPAuthTokenVerificationException(
MCPAuthTokenVerificationExceptionCode.INVALID_TOKEN,
cause=e,
)
# 处理请求过程中可能发生的其他异常
except Exception as e:
raise MCPAuthTokenVerificationException(
MCPAuthTokenVerificationExceptionCode.TOKEN_VERIFICATION_FAILED,
cause=e,
)
import { MCPAuthTokenVerificationError } from 'mcp-auth';
/**
* 通过从授权服务器获取用户信息来校验提供的 Bearer 令牌 (Access token)。
* 如果令牌有效,则返回包含用户信息的 `AuthInfo` 对象。
*/
const verifyToken = async (token) => {
const { issuer, userinfoEndpoint } = mcpAuth.config.server.metadata;
if (!userinfoEndpoint) {
throw new Error('Server metadata 中未配置 userinfo endpoint');
}
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' 是主体 (Subject)(用户 ID)的标准声明 (Claim)
clientId: '', // 本示例未使用 Client ID,如有需要可设置
scopes: [],
claims: userInfo,
};
};
如前所述,OAuth 2.0 没有定义获取用户身份信息的标准方式。以下代码假设你的提供商有专门的端点用于通过访问令牌 (Access token) 获取用户身份信息。请查阅你的提供商文档获取具体端点,并将 userinfo 端点变量替换为正确的 URL。
- Python
- Node.js
更新 whoami.py
,加入 MCP Auth 配置:
from mcpauth import MCPAuth
from mcpauth.config import AuthServerType
from mcpauth.utils import fetch_server_config
auth_issuer = '<issuer-endpoint>' # 替换为你的发行者 (Issuer) 端点
auth_server_config = fetch_server_config(auth_issuer, type=AuthServerType.OAUTH)
mcp_auth = MCPAuth(server=auth_server_config)
更新 whoami.js
,加入 MCP Auth 配置:
import { MCPAuth, fetchServerConfig } from 'mcp-auth';
const authIssuer = '<issuer-endpoint>'; // 替换为你的发行者 (Issuer) 端点
const mcpAuth = new MCPAuth({
server: await fetchServerConfig(authIssuer, { type: 'oauth' }),
});
在某些情况下,提供方的响应可能格式错误或不符合预期的元数据格式。如果你确信该提供方是合规的,你可以通过配置选项对元数据进行转译:
- Python
- Node.js
mcp_auth = MCPAuth(
server=fetch_server_config(
# ...其他选项
transpile_data=lambda data: {**data, 'response_types_supported': ['code']}
)
)
const mcpAuth = new MCPAuth({
server: await fetchServerConfig(authIssuer, {
// ...其他选项
transpileData: (data) => ({ ...data, response_types_supported: ['code'] }),
}),
});
如果你的提供商不支持 OAuth 2.0 授权服务器元数据 (Authorization Server Metadata),你可以手动指定元数据 URL 或端点。详见 其他初始化 MCP Auth 的方式。
现在,我们需要创建一个自定义访问令牌 (Access token) 验证器,用于通过 MCP inspector 提供的访问令牌 (Access token) 从授权 (Authorization) 服务器获取用户身份信息。
- 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:
"""
通过从授权 (Authorization) 服务器获取用户信息来验证提供的 Bearer 令牌 (Token)。
如果令牌 (Token) 有效,则返回包含用户信息的 `AuthInfo` 对象。
:param token: 从 MCP inspector 接收到的 Bearer 令牌 (Token)。
"""
try:
# 以下代码假设你的授权 (Authorization) 服务器有一个用于通过授权 (Authorization) 流程颁发的访问令牌 (Access token)
# 获取用户信息的端点。请根据你的提供商 API 调整 URL 和 headers。
response = requests.get(
"https://your-authorization-server.com/userinfo",
headers={"Authorization": f"Bearer {token}"},
)
response.raise_for_status() # 确保 HTTP 错误时抛出异常
json = response.json() # 解析 JSON 响应
# 以下代码假设用户信息响应对象中有一个 'sub' 字段用于标识用户。
# 你可能需要根据你的提供商 API 进行调整。
return AuthInfo(
token=token,
subject=json.get("sub"),
issuer=auth_issuer, # 使用配置的发行者 (Issuer)
claims=json, # 包含端点返回的所有声明 (Claims)(JSON 字段)
)
# `AuthInfo` 是一个 Pydantic 模型,因此验证错误通常意味着响应结构与预期不符
except pydantic.ValidationError as e:
raise MCPAuthTokenVerificationException(
MCPAuthTokenVerificationExceptionCode.INVALID_TOKEN,
cause=e,
)
# 处理请求过程中可能发生的其他异常
except Exception as e:
raise MCPAuthTokenVerificationException(
MCPAuthTokenVerificationExceptionCode.TOKEN_VERIFICATION_FAILED,
cause=e,
)
import { MCPAuthTokenVerificationError } from 'mcp-auth';
/**
* 通过从授权 (Authorization) 服务器获取用户信息来验证提供的 Bearer 令牌 (Token)。
* 如果令牌 (Token) 有效,则返回包含用户信息的 `AuthInfo` 对象。
*/
const verifyToken = async (token) => {
// 以下代码假设你的授权 (Authorization) 服务器有一个用于通过授权 (Authorization) 流程颁发的访问令牌 (Access token)
// 获取用户信息的端点。请根据你的提供商 API 调整 URL 和 headers。
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();
// 以下代码假设用户信息响应对象中有一个 'sub' 字段用于标识用户。
// 你可能需要根据你的提供商 API 进行调整。
if (typeof userInfo !== 'object' || userInfo === null || !('sub' in userInfo)) {
throw new MCPAuthTokenVerificationError('invalid_token', response);
}
return {
token,
issuer: authIssuer,
subject: String(userInfo.sub), // 根据你的提供商用户 ID 字段进行调整
clientId: '', // 本示例未使用 Client ID,如有需要可设置
scopes: [],
claims: userInfo,
};
};
更新 MCP 服务器 (Update MCP server)
我们快完成了!现在需要更新 MCP 服务器,应用 MCP Auth 路由和中间件函数,并让 whoami
工具返回实际的用户身份信息。
- Python
- Node.js
@mcp.tool()
def whoami() -> dict[str, Any]:
"""返回当前用户信息的工具。"""
return (
mcp_auth.auth_info.claims
if mcp_auth.auth_info # 由 Bearer auth 中间件填充
else {"error": "Not authenticated"}
)
# ...
bearer_auth = Middleware(mcp_auth.bearer_auth_middleware(verify_access_token))
app = Starlette(
routes=[
# 添加元数据路由 (`/.well-known/oauth-authorization-server`)
mcp_auth.metadata_route(),
# 用 Bearer auth 中间件保护 MCP 服务器
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));
检查点:带认证 (Authentication) 运行 whoami
工具 (Checkpoint: Run the whoami
tool with authentication)
重启你的 MCP 服务器,并在浏览器中打开 MCP inspector。当你点击 "Connect" 按钮时,应会被重定向到授权 (Authorization) 服务器的登录页面。
登录后返回 MCP inspector,重复上一个检查点的操作运行 whoami
工具。这一次,你应该能看到授权 (Authorization) 服务器返回的用户身份信息。
- Python
- Node.js
完整 MCP 服务器(OIDC 版本)代码请参考 MCP Auth Python SDK 仓库。
完整 MCP 服务器(OIDC 版本)代码请参考 MCP Auth Node.js SDK 仓库。该目录包含 TypeScript 和 JavaScript 版本。
结语 (Closing notes)
🎊 恭喜你!你已成功完成本教程。让我们回顾一下所做的内容:
- 搭建了带有
whoami
工具的基础 MCP 服务器 - 使用 MCP Auth 将 MCP 服务器与授权 (Authorization) 服务器集成
- 配置 MCP Inspector 以认证 (Authentication) 用户并获取其身份信息
你还可以探索一些进阶主题,包括:
- 使用 JWT (JSON Web Token) 进行认证 (Authentication) 和授权 (Authorization)
- 利用 资源指示器 (resource indicators, RFC 8707) 指定被访问的资源
- 实现自定义访问控制机制,如 基于角色的访问控制 (RBAC) 或 基于属性的访问控制 (ABAC)
欢迎查阅更多教程和文档,充分发挥 MCP Auth 的能力。