
Semantic Kernel 中的 MCP OAuth 认证实战基于 RFC 9728 的授权服务器与资源服务器分离方案【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel本文围绕仓库内python/samples/demos/mcp_with_oauth演示项目系统讲解如何在 Semantic Kernel 中通过 OAuth 2.0 认证安全地连接 Model Context ProtocolMCP服务器。该示例按 RFC 9728 规范将授权服务器Authorization Server, AS与资源服务器Resource Server, RS分离完整覆盖从启动 AS、启动受保护的 MCP 资源服务器到让 SK Agent 自动完成 OAuth 授权码流程并调用受保护工具的全过程。读完本文你将掌握 MCP OAuth 认证的完整运行命令、客户端认证组件的接入方式以及认证背后的源码级实现原理。一、为什么需要 OAuth 保护 MCP 服务器MCP 服务器向外暴露工具Tool与资源Resource一旦这些工具涉及用户私有数据或敏感系统信息就必须验证调用者的身份。OAuth 2.0 是业界标准的授权协议而 MCP 规范基于 RFC 9728Protected Resource Metadata for OAuth 2.0推荐了一种新的部署形态授权服务器AS独立负责客户端注册、授权码签发、令牌发放与令牌检查Introspection不承载业务数据资源服务器RS即实际的 MCP 服务器负责执行业务工具但只信任 AS 签发的令牌。这种 AS/RS 分离的架构可以类比身份证签发机构与门禁系统的关系门禁只负责核验身份证真伪不负责发证。对企业而言AS 可以复用已有的身份体系如 Auth0、Entra ID 等多个 MCP 资源服务器共用同一个 AS实现统一认证与集中管控。本演示README的核心代码源自官方 MCP Python SDK 的simple-auth示例服务端与部分客户端认证代码均基于该示例改造而来目的是演示 SK 客户端如何与这类受 OAuth 保护的 MCP 服务器完成连接。二、演示架构全景整个演示由三个进程组成端口分配如下组件端口角色启动命令Authorization Server9000客户端注册、授权、令牌签发、/introspect令牌检查uv run mcp-simple-auth-as --port9000Resource ServerMCP Server8001提供受保护的 MCP 工具向 AS 校验令牌uv run mcp-simple-auth-rs --port8001 --auth-serverhttp://localhost:9000 --transportstreamable-httpSK Agent 客户端3030回调通过MCPStreamableHttpPlugin连接 RS并处理 OAuth 回调uv --env-file .env run agent认证链路为SK 客户端 → 资源服务器8001→ 授权服务器9000。RS 自身不存储用户与令牌而是把令牌交给 AS 的/introspect端点校验RFC 7662 Token Introspection校验通过后才允许调用工具。三、分步运行完整指南Step 1启动授权服务器AS# 进入 simple-auth 目录 cd samples/demos/mcp_with_oauth/server # 在 9000 端口启动授权服务器 uv run mcp-simple-auth-as --port9000AS 提供的能力详见 auth_server.pyOAuth 2.0 完整流程客户端注册/register、授权/authorize、令牌交换/token基于简单凭据的认证内置演示账号demo_user / demo_password无需外部身份提供商令牌检查端点为资源服务器提供 RFC 7662 风格的/introspect端点RS 无需直接访问令牌存储即可校验令牌有效性。--port参数默认值为 9000服务启动后监听http://localhost:9000同时挂载/login登录页与/login/callback登录回调两个路由。Step 2启动资源服务器MCP Server在另一个终端中执行# 在另一个终端进入 simple-auth 目录 cd samples/demos/mcp_with_oauth/server # 在 8001 端口启动资源服务器并连接到授权服务器使用 streamable-http 传输 uv run mcp-simple-auth-rs --port8001 --auth-serverhttp://localhost:9000 --transportstreamable-http # 生产环境推荐启用 RFC 8707 严格资源校验 uv run mcp-simple-auth-rs --port8001 --auth-serverhttp://localhost:9000 --transportstreamable-http --oauth-strict资源服务器命令行参数定义见 server.py参数默认值说明--port8001资源服务器监听端口--auth-serverhttp://localhost:9000授权服务器地址资源服务器会据此推导 introspection 端点为{auth-server}/introspect--transportstreamable-http传输协议可选sse或streamable-http--oauth-strict关闭开启后启用 RFC 8707 资源audience严格校验生产环境推荐开启资源服务器暴露的唯一工具是get_time返回服务器当前时间。它的存在是为了演示未经 OAuth 认证的调用者无法访问该工具见 server.py 中的工具定义与注释。Step 3配置客户端并运行 Agent客户端使用 Azure OpenAI 作为 Agent 的对话服务。有两种配置方式方式一使用全局 venv 中已有的 Azure 配置方式二在samples/demos/mcp_with_oauth/agent目录下创建.env文件填入以下内容AZURE_OPENAI_ENDPOINT AZURE_OPENAI_CHAT_DEPLOYMENT_NAME然后运行cd samples/demos/mcp_with_oauth # 使用 streamable HTTP 插件启动 agent并加载 .env 中的 Azure 配置 uv --env-file .env run agent也可以直接打开 agent/main.py 在 IDE 中运行。Agent 启动后会自动打开浏览器跳转到授权页面使用演示账号demo_user / demo_password登录并完成授权随后向 Agent 提问What time is it?Agent 将调用受保护的get_time工具并返回当前时间。四、客户端源码解析SK 如何接入 OAuth 认证客户端代码agent/main.py展示了 SK 连接受保护 MCP 服务器的完整模式核心是三个自定义组件 一个插件。4.1 令牌存储InMemoryTokenStorageOAuth 客户端需要持久化令牌与客户端注册信息SK 通过TokenStorage抽象来管理class InMemoryTokenStorage(TokenStorage): Simple in-memory token storage implementation. def __init__(self): self._tokens: OAuthToken | None None self._client_info: OAuthClientInformationFull | None None async def get_tokens(self) - OAuthToken | None: return self._tokens async def set_tokens(self, tokens: OAuthToken) - None: self._tokens tokens async def get_client_info(self) - OAuthClientInformationFull | None: return self._client_info async def set_client_info(self, client_info: OAuthClientInformationFull) - None: self._client_info client_info示例采用内存存储进程结束后令牌即丢失。接入真实场景时应把TokenStorage换成基于数据库或文件系统的实现让令牌跨进程、跨会话复用尤其要持久化 refresh token。4.2 本地回调服务器CallbackServerOAuth 授权码流程要求客户端提供一个回调地址接收授权码。示例在本地 3030 端口启动了一个轻量 HTTP 服务器CallbackServer在后台线程中监听http://localhost:3030/callback收到code参数提取授权码与state返回 Authorization Successful! 页面并自动关闭窗口收到error参数记录错误并返回失败页面其他请求返回 404。wait_for_callback(timeout300)会阻塞等待授权码超时或出错则抛出异常main.py。4.3 组装 OAuth 客户端提供者client_metadata_dict { client_name: Simple Auth Client, redirect_uris: [http://localhost:3030/callback], grant_types: [authorization_code, refresh_token], response_types: [code], token_endpoint_auth_method: client_secret_post, } async def _default_redirect_handler(authorization_url: str) - None: Default redirect handler that opens the URL in a browser. print(fOpening browser for authorization: {authorization_url}) webbrowser.open(authorization_url) oauth_auth OAuthClientProvider( server_urlhttp://localhost:9000, client_metadataOAuthClientMetadata.model_validate(client_metadata_dict), storageInMemoryTokenStorage(), redirect_handler_default_redirect_handler, callback_handlercallback_handler, )关键参数说明参数作用server_url授权服务器地址http://localhost:9000客户端据此发现授权端点client_metadata客户端元数据声明重定向 URI、支持的授权类型授权码 刷新令牌与令牌端点认证方式client_secret_poststorage令牌与客户端信息存储redirect_handler拿到授权 URL 后如何跳转示例用webbrowser.open打开浏览器callback_handler等待并返回授权码与 state 的异步回调4.4 通过MCPStreamableHttpPlugin连接受保护服务器async with MCPStreamableHttpPlugin( nameAuthServer, descriptionAuth Server Plugin, urlhttp://localhost:8001/mcp, authoauth_auth, timeouttimedelta(seconds60), ) as oath_plugin: agent ChatCompletionAgent( serviceAzureChatCompletion(credentialAzureCliCredential()), nameProtectedAgent, instructionsAnswer the users questions., plugins[oath_plugin], ) ... response await agent.get_response(messagesuser_input, threadthread)MCPStreamableHttpPlugin定义于 mcp.py构造参数包括name插件名、urlMCP 服务器地址、description、load_tools/load_prompts是否加载 MCP 工具与提示词默认均为True、request_timeout、timeout、headers等未识别的关键字参数会透传给底层streamablehttp_client——示例中的authoauth_auth正是通过这一透传机制注入到 MCP 传输层使插件在连接资源服务器时自动完成 OAuth 流程。工具调用成功后输出类似见 main.py 中的预期输出注释️ Started callback server on http://localhost:3030 Opening browser for authorization: http://localhost:9000/authorize?response_type... ⏳ Waiting for authorization callback... # User: What time is it? # ProtectedAgent: The current time is 16:54:55 (4:54 PM) on July 10, 2025, in the UTC timezone.五、服务端原理授权服务器与资源服务器如何协同5.1 授权服务器凭据登录 → 授权码 → 访问令牌授权服务器实现于 auth_server.py 与 simple_auth_provider.py其完整授权链路由以下步骤构成客户端注册register_client把客户端元数据存入内存字典供后续校验发起授权authorize生成随机state并保存redirect_uri、code_challenge、resourceRFC 8707等上下文返回指向/login的登录页地址登录校验/login/callback收到表单提交的用户名密码与 state与SimpleAuthSettings中的演示凭据比对默认demo_user / demo_password可通过MCP_DEMO_USERNAME、MCP_DEMO_PASSWORD环境变量覆盖校验通过后生成mcp_{hex}格式的授权码有效期 300 秒并重定向回客户端回调地址令牌交换exchange_authorization_code用授权码换取访问令牌令牌格式为mcp_{32位hex}有效期 3600 秒同时记录令牌与用户、resource 的映射刷新令牌示例刻意未实现load_refresh_token返回Noneexchange_refresh_token抛出NotImplementedError仅支持授权码流程。5.2 令牌检查端点/introspect资源服务器需要一种不直接访问令牌存储的校验方式AS 因此暴露了 RFC 7662 风格的 introspection 端点auth_server.pyasync def introspect_handler(request: Request) - Response: form await request.form() token form.get(token) if not token or not isinstance(token, str): return JSONResponse({active: False}, status_code400) access_token await oauth_provider.load_access_token(token) if not access_token: return JSONResponse({active: False}) return JSONResponse({ active: True, client_id: access_token.client_id, scope: .join(access_token.scopes), exp: access_token.expires_at, iat: int(time.time()), token_type: Bearer, aud: access_token.resource, # RFC 8707 audience claim })响应中的active字段是校验结果的核心令牌不存在或已过期超过 3600 秒都会返回active: falseaud字段回传令牌签发时的 resource供 RS 做 RFC 8707 严格校验。5.3 资源服务器使用 IntrospectionTokenVerifier 校验令牌资源服务器server.py通过IntrospectionTokenVerifiertoken_verifier.py完成令牌校验校验逻辑防 SSRF拒绝指向非https://、非localhost/127.0.0.1的 introspection 端点安全 HTTP 客户端设置 10 秒超时、连接池上限与强制 SSL 校验调用 introspection 端点POSTtoken字段到{auth-server}/introspect状态码非 200 或active为 false 则拒绝RFC 8707 资源校验仅在--oauth-strict开启时生效对比令牌aud声明与 RS 自身的 resource URL使用check_resource_allowed进行层级匹配无aud声明的令牌直接判定无效。从该实现可以推断生产环境的令牌校验还应补充连接池复用、限流与重试、更完善的错误处理等能力源码注释中也明确列出了这些待办项。此外ResourceServerSettings支持MCP_RESOURCE_前缀的环境变量覆盖如MCP_RESOURCE_OAUTH_STRICT便于容器化部署时注入配置。六、工程实践要点6.1 目录与入口速查客户端agent/main.py入口agent定义于 pyproject.toml 的[project.scripts]agent agent.main:cli授权服务器auth_server.py入口mcp-simple-auth-as资源服务器server.py入口mcp-simple-auth-rs依赖声明客户端依赖semantic-kernel[mcp]与click服务端依赖mcp、pydantic、starlette、uvicorn等详见 server/pyproject.toml。6.2 将演示落地为生产方案的清单替换令牌存储将InMemoryTokenStorage换为持久化实现支持 refresh token 复用避免每次启动重复授权对接企业身份体系AS 的SimpleOAuthProvider只是演示实现生产环境应替换为 Auth0、Entra ID 等企业级授权服务器强制开启严格校验资源服务器务必启用--oauth-strict确保令牌携带正确的 resourceaudience传输与网络安全演示使用本地明文 HTTP生产环境应使用 HTTPS 并部署在可信网络边界内服务端安全加固IntrospectionTokenVerifier的注释明确指出生产实现应考虑连接池复用、限流重试与更细化的配置。6.3 延伸阅读本演示只展示了 MCP 客户端认证的一条路径。仓库中还有更多 MCP 与 SK 集成的样例可对照阅读agent_with_http_mcp_plugin.py无需认证的 streamable HTTP MCP 插件连接方式可与本文对比理解auth参数的差异mcp_as_plugin.py 与 agent_with_mcp_agent.py其他 MCP 接入形态agent_with_mcp_sampling.pyMCP sampling 机制的授权控制。七、总结本演示项目完整呈现了 OAuth 2.0 保护下的 MCP 服务器接入 Semantic Kernel 的标准路径独立授权服务器签发与检查令牌、资源服务器通过 introspection 校验令牌、SK 客户端通过OAuthClientProvider与MCPStreamableHttpPlugin自动完成授权码流程。掌握这一模式后你可以将任何符合 RFC 9728/7662/8707 规范的受保护 MCP 服务器无缝接入 SK Agent让大模型应用在获得 MCP 工具生态能力的同时守住身份认证与授权这道安全边界。【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考