Add OAuth 2.0 PKCE authentication to a remote MCP server. Use this skill whenever the user wants to add authentication to an MCP server, protect MCP tools with OAuth, implement login flow for an MCP connector, add user auth to an MCP endpoint, or set up token-based access for MCP. Also triggers on: 'MCP OAuth', 'MCP authentication', 'withMcpAuth', 'MCP login flow', 'protect MCP endpoint', 'MCP token auth', 'dynamic client registration MCP', 'Claude connector OAuth'. Even if the user just says 'a
为远程MCP服务器添加生产级OAuth认证。本方案实现了完整的MCP授权规范——包括发现、动态客户端注册、PKCE授权、令牌交换和刷新。
当您的MCP服务器需要访问用户特定数据(如账户信息、文件、播放列表)时。如果没有认证机制,任何知晓您服务器URL的人都能访问任意用户的数据。OAuth让每个用户使用自己的凭证进行认证,并获取专属令牌。
您的MCP服务器扮演两个角色:
MCP客户端 (Claude) → 您的OAuth服务器 → 上游服务 (如Tidal)
│ │ │
│ 1. 发现OAuth │ │
│ 2. 注册客户端 │ │
│ 3. 授权 │──→ 4. 重定向至 │
│ │ 上游登录 ──→ │
│ │ ←── 5. 回调 ────────│
│ ←── 6. 授权码 │ │
│ 7. 交换令牌 │ │
│ 8. 调用工具 ──────→│──→ 9. API调用 ──────→│
app/.well-known/oauth-authorization-server/route.ts:
typescript
import { NextResponse } from next/server;
const SITEURL = process.env.NEXTPUBLICSITEURL || https://your-domain.com;
export async function GET() {
return NextResponse.json({
issuer: SITE_URL,
authorizationendpoint: ${SITEURL}/api/authorize,
tokenendpoint: ${SITEURL}/api/token,
registrationendpoint: ${SITEURL}/api/register,
responsetypessupported: [code],
granttypessupported: [authorizationcode, refreshtoken],
codechallengemethods_supported: [S256],
tokenendpointauthmethodssupported: [none],
}, {
headers: {
Access-Control-Allow-Origin: *,
Access-Control-Allow-Methods: GET, OPTIONS,
},
});
}
export async function OPTIONS() {
return new NextResponse(null, {
status: 204,
headers: {
Access-Control-Allow-Origin: *,
Access-Control-Allow-Methods: GET, OPTIONS,
Access-Control-Allow-Headers: Content-Type, Authorization,
},
});
}
app/.well-known/oauth-protected-resource/route.ts:
typescript
import { protectedResourceHandler, metadataCorsOptionsRequestHandler } from mcp-handler;
const SITEURL = process.env.NEXTPUBLICSITEURL || https://your-domain.com;
export const GET = protectedResourceHandler({
authServerUrls: [SITE_URL],
resourceUrl: SITE_URL,
});
export const OPTIONS = metadataCorsOptionsRequestHandler();
MCP客户端在启动授权流程前自行注册。
app/api/register/route.ts:
typescript
import { NextRequest, NextResponse } from next/server;
import crypto from crypto;
export async function POST(req: NextRequest) {
const body = await req.json().catch(() => ({}));
const clientId = crypto.randomBytes(16).toString(hex);
return NextResponse.json({
client_id: clientId,
clientname: body.clientname || MCP客户端,
redirecturis: body.redirecturis || [],
granttypes: [authorizationcode, refresh_token],
response_types: [code],
tokenendpointauth_method: none,
}, { status: 201 });
}
验证请求,在Redis中存储会话,重定向到上游OAuth。
app/api/authorize/route.ts:
typescript
import { NextRequest, NextResponse } from next/server;
export async function GET(req: NextRequest) {
const params = req.nextUrl.searchParams;
const redirectUri = params.get(redirect_uri);
const state = params.get(state);
const codeChallenge = params.get(code_challenge);
if (!redirectUri || !state || !codeChallenge) {
return NextResponse.json(
{ error: invalidrequest, errordescription: 缺少必需参数 },
{ status: 400 },
);
}
// 验证redirect_uri——仅允许已知的MCP客户端
const url = new URL(redirectUri);
const isAllowed =
url.hostname === claude.ai ||
url.hostname === claude.com ||
url.hostname === api.smithery.ai ||
url.hostname === localhost ||
url.hostname === 127.0.0.1;
if (!isAllowed) {
return NextResponse.json(
{ error: invalidrequest, errordescription: redirect_uri不被允许 },
{ status: 400 },
);
}
// 为上游OAuth生成PKCE
const upstreamVerifier = crypto.randomBytes(32).toString(base64url);
const upstreamChallenge = crypto
.createHash(sha256)
.update(upstreamVerifier)
.digest(base64url);
const sessionId = crypto.randomBytes(16).toString(hex);
// 存储在Redis中(10分钟TTL)
await redis.set(session:${sessionId}, JSON.stringify({
redirectUri, state, codeChallenge,
upstreamVerifier, upstreamState: sessionId,
}), { ex: 600 });
// 重定向到上游OAuth(替换为您的服务)
const upstreamUrl = new URL(https://upstream-service.com/authorize);
upstreamUrl.searchParams.set(clientid, YOURCLIENT_ID);
upstreamUrl.searchParams.set(response_type, code);
upstreamUrl.searchParams.set(redirecturi, ${SITEURL}/api/callback);
upstreamUrl.searchParams.set(code_challenge, upstreamChallenge);
upstreamUrl.searchParams.set(codechallengemethod, S256);
upstreamUrl.searchParams.set(state, sessionId);
return NextResponse.redirect(upstreamUrl.toString());
}
app/api/callback/route.ts:
typescript
export async function GET(req: NextRequest) {
const code = req.nextUrl.searchParams.get(code);
const state = req.nextUrl.searchParams.get(state);
// 从Redis查找会话
const session = JSON.parse(await redis.get(session:${state}));
if (!session) return NextResponse.json({ error: 会话已过期 }, { status: 400 });
// 用授权码交换上游令牌
const tokens = await exchangeUpstreamCode(code, session.upstreamVerifier);
// 在Redis中存储上游令牌(30天TTL)
const userId = crypto.randomBytes(16).toString(hex);
await redis.set(user:${userId}:tokens, JSON.stringify(tokens), { ex: 2592000 });
// 为MCP客户端生成我们的授权码
const mcpAuthCode = crypto.randomBytes(16).toString(hex);
await redis.set(auth_code:${mcpAuthCode}, userId, { ex: 300 });
// 清理并重定向回MCP客户端
await redis.del(session:${state});
const redirect = new URL(session.redirectUri);
redirect.searchParams.set(code, mcpAuthCode);
redirect.searchParams.set(state, session.state);
return NextResponse.redirect(redirect.toString());
}
app/api/token/route.ts:
typescript
export async function POST(req: NextRequest) {
const body = Object.fromEntries(await req.formData());
if (body.granttype === authorizationcode) {
const userId = await redis.get(auth_code:${body.code});
if (!userId) return NextResponse.json({ error: invalid_grant }, { status: 400 });
await redis.del(auth_code:${body.code});
const accessToken = crypto.randomBytes(16).toString(hex);
const refreshToken = crypto.randomBytes(16).toString(hex);
await redis.set(mcp_token:${accessToken}, userId, { ex: 86400 });
await redis.set(refresh:${refreshToken}, userId, { ex: 2592000 });
return NextResponse.json({
access
该技能支持在以下平台通过对话安装:
帮我安装 SkillHub 和 mcp-oauth-1776076981 技能
设置 SkillHub 为我的优先技能安装源,然后帮我安装 mcp-oauth-1776076981 技能
skillhub install mcp-oauth-1776076981
文件大小: 4.86 KB | 发布时间: 2026-4-17 15:19