新闻详情

新闻详情

首页 / 资讯中心 / 详情

开源模型应用落地-模型上下文协议(MCP)-安全认证的创新与实践探索(十):用 fastmcp 给 MCP 服务加一层可验证的鉴权

发布时间:2026/9/28 19:36:55来源:尧图网络
开源模型应用落地-模型上下文协议(MCP)-安全认证的创新与实践探索(十):用 fastmcp 给 MCP 服务加一层可验证的鉴权
1. 为什么 MCP 服务裸奔上线迟早要出事MCP模型上下文协议这两年火得很快它把大模型和外部工具、数据源之间的调用方式统一成了 JSON-RPC 2.0 那一套本地走 stdio、远程走 HTTP/SSE模型能自动发现工具、自动调用。但很多人第一次把 MCP 服务跑起来的时候注意力全在“工具能不能被调用成功”上鉴权这块基本是空的——服务监听在0.0.0.0:9999谁扫到端口谁就能调你的add、query_db、send_mail。我见过最典型的场景开发同学在内网机器上跑了一个 fastmcp 服务本意是给自己团队的 Agent 用结果端口没做任何校验隔壁组的脚本误连上来直接把测试库的数据删了一批。MCP 的定位是“AI 领域的 USB-C 接口”接口越通用越需要一层身份校验否则它就是一个对全网开放的 RPC 入口。这篇要解决的就是这件事用 fastmcp 给 MCP 服务加一层可验证的鉴权覆盖本地调试和内网部署两种场景。你会拿到能直接跑通的最小配置——服务端装饰器怎么写、客户端 header 怎么带、TaoToken 的统一 Key/API 通道接在哪、用 curl 和 MCP 客户端各验证一次成功与失败。适合已经在用开源模型接 MCP、但还没做访问控制的人。2. 前置准备fastmcp 版本、TaoToken 通道与目录结构2.1 版本选择别踩最新的坑fastmcp 迭代很快2.5.x 之后部分 API 有调整示例代码直接跑会报get_http_request找不到或者 context 取不到。建议锁在2.2.7和本文一致uv pip install fastmcp2.2.7如果你用 pippip install fastmcp2.2.7MCP 基础环境Python 版本、mcp 包、SSE 依赖先按官方文档装好这里不重复。装完确认一下python -c import fastmcp; print(fastmcp.__version__)输出2.2.7就对了。2.2 TaoToken 统一 Key/API 通道接在哪鉴权这件事分两层一层是你自己 MCP 服务的入口校验secretId/secretKey另一层是 MCP 服务背后调用模型时的凭证管理。第二层如果每个工具都硬编码一个模型 Key泄露风险极高。我的做法是把模型调用统一走 TaoToken 的 API 通道MCP 服务只持有 TaoToken 的 Key模型侧换供应商不用改 MCP 代码。TaoToken 的 API 入口是https://taotoken.net/api控制台里可以生成和管理 Key。你需要在 TaoToken 控制台创建一个 Key然后把它放进 MCP 服务的环境变量而不是写死在代码里。这样 MCP 服务对外校验的是自己的 secretId/secretKey对内调模型用的是 TaoToken Key两层解耦。具体操作路径登录 TaoToken 控制台 → API Keys 页面 → 新建 Key → 复制保存。这个 Key 后面会写进.env通过os.getenv读取。2.3 目录结构mcp-auth-demo/ ├── server.py # MCP 服务端 ├── client.py # MCP 客户端 ├── .env # 环境变量不提交 git ├── config.toml # 服务端配置骨架 └── settings.json # 客户端配置骨架3. 可复制配置config.toml、settings.json 与服务端鉴权代码3.1 config.toml 骨架服务端的配置我习惯用 TOML读起来清楚也方便区分本地和内网两套[server] name Demo host 0.0.0.0 port 9999 transport sse [auth] # 本地调试用固定值内网部署改成从环境变量注入 secret_id 123456789 secret_key xxxxxxxxxxxxx # 鉴权失败时返回的提示别暴露内部细节 fail_message authentication failed [model] # 模型调用统一走 TaoToken 通道 base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY注意secret_key在真实部署里不要写进 TOML 提交到仓库用环境变量覆盖。TOML 里只留占位符。3.2 settings.json 骨架客户端客户端配置对应服务端的鉴权字段{ mcpServers: { demo: { url: http://localhost:9999/sse, headers: { secretId: 123456789, secretKey: xxxxxxxxxxxxx } } }, model: { baseUrl: https://taotoken.net/api, apiKeyEnv: TAOTOKEN_API_KEY } }这个settings.json可以直接被支持 MCP 的客户端读取也可以作为你手写客户端时的参数来源。3.3 服务端鉴权装饰器核心思路写一个auth()装饰器工厂在工具函数执行前从 HTTP 请求头里取secretId和secretKey校验不通过直接返回失败信息不进入业务逻辑。# -*- coding: utf-8 -*- import os import inspect from functools import wraps from fastmcp import FastMCP mcp FastMCP(nameDemo, port9999) DEFAULT_TRANSPORT sse # 从环境变量读取避免硬编码 VALID_SECRET_ID os.getenv(MCP_SECRET_ID, 123456789) VALID_SECRET_KEY os.getenv(MCP_SECRET_KEY, xxxxxxxxxxxxx) def auth(): 认证装饰器工厂 校验 HTTP 请求头中的 secretId / secretKey def wrapper(func): wraps(func) async def wrapped(*args, **kwargs): ctx mcp.get_context() request ctx.get_http_request() if secretId not in request.headers or secretKey not in request.headers: return authentication failed if (request.headers[secretId] ! VALID_SECRET_ID or request.headers[secretKey] ! VALID_SECRET_KEY): return authentication failed if inspect.iscoroutinefunction(func): return await func(*args, **kwargs) return func(*args, **kwargs) return wrapped return wrapper mcp.tool() auth() def add(a: int, b: int) - int: 两个整数相加 print(fa: {a}, b: {b}) return a b if __name__ __main__: print(fstart mcp server, transport: {DEFAULT_TRANSPORT}...) mcp.run(transportDEFAULT_TRANSPORT)几个关键点mcp.get_context()拿到当前请求上下文ctx.get_http_request()拿到原始 HTTP 请求对象header 从这里读。装饰器顺序是mcp.tool()在外、auth()在内这样 fastmcp 注册的是被 auth 包装后的函数。3.4 客户端代码# -*- coding: utf-8 -*- import asyncio from mcp import ClientSession from mcp.client.sse import sse_client SSE_URL http://localhost:9999/sse async def run(): async with sse_client( urlSSE_URL, headers{secretId: 123456789, secretKey: xxxxxxxxxxxxx} ) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools await session.list_tools() print(ftools: {tools}) result await session.call_tool(add, arguments{a: 1, b: 2}) print(fresult: {result.content}) if __name__ __main__: asyncio.run(run())4. 验证请求curl 与 MCP 客户端各跑一次成功/失败4.1 启动服务端python server.py看到start mcp server, transport: sse...就说明起来了默认监听 9999。4.2 用 curl 验证鉴权成功SSE 端点先建立连接再发 JSON-RPC 请求。最简验证方式是直接打 SSE 端点看是否接受 headercurl -N -H secretId: 123456789 \ -H secretKey: xxxxxxxxxxxxx \ -H Accept: text/event-stream \ http://localhost:9999/sse成功时你会看到 SSE 流打开服务端返回event: endpoint之类的握手信息连接保持不断开。4.3 用 curl 验证鉴权失败把 secretKey 改错curl -N -H secretId: 123456789 \ -H secretKey: wrong_key \ -H Accept: text/event-stream \ http://localhost:9999/sse因为鉴权在工具调用层SSE 握手本身可能仍会建立但当你通过该连接调用add工具时返回的 content 会是authentication failed。更直接的验证是走 MCP 客户端。4.4 MCP 客户端验证成功python client.py预期输出tools: [Tool(nameadd, ...)] result: [TextContent(typetext, text3)]服务端打印a: 1, b: 2。4.5 MCP 客户端验证失败把client.py里的secretKey改成wrong_key重跑result: [TextContent(typetext, textauthentication failed)]服务端不会打印a: 1, b: 2说明请求在装饰器层就被拦下了业务函数没执行。这就是可验证鉴权的意义——失败路径明确、可观测。5. 本篇常见错排查5.1AttributeError: Context object has no attribute get_http_requestfastmcp 版本不对。2.2.7 有这个方法2.5.x 改了 API。降级uv pip install fastmcp2.2.7 --force-reinstall5.2 鉴权永远失败header 明明带了检查两点一是客户端 header 的 key 大小写HTTP header 在部分实现里会被规范化成小写request.headers取的时候用secretId还是secretid取决于底层库。稳妥做法是统一小写比较headers {k.lower(): v for k, v in request.headers.items()} if headers.get(secretid) ! VALID_SECRET_ID: return authentication failed二是确认请求真的走了 SSE 而不是 stdiostdio 模式下没有 HTTP request 对象get_http_request()会抛异常。5.3 装饰器顺序写反导致鉴权不生效auth()必须在mcp.tool()下面即先被 auth 包装再被 tool 注册。写反了 fastmcp 注册的是原始函数鉴权层被绕过。验证方法故意传错 Key如果还能返回3就是顺序问题。5.4 内网部署时 secretKey 泄露别把真实 Key 写进config.toml提交。用环境变量export MCP_SECRET_IDyour_id export MCP_SECRET_KEYyour_key代码里os.getenv读取TOML 只留占位。TaoToken 的 Key 同理走TAOTOKEN_API_KEY环境变量MCP 服务本身不落盘。5.5 模型调用 401MCP 服务背后的模型调用如果返回 401先确认 TaoToken Key 是否有效、是否放进了环境变量。可以在服务启动时加一行日志确认读取到了 Key只打印前几位别打全key os.getenv(TAOTOKEN_API_KEY, ) print(ftaotoken key loaded: {key[:6]}...)6. 把鉴权层固定下来再谈扩展到这一步你的 MCP 服务已经有一层可验证的入口鉴权header 带 secretId/secretKey装饰器拦截失败路径明确。本地调试用固定值内网部署换成环境变量注入模型调用统一走 TaoToken 的 API 通道MCP 服务只持有通道 Key不直接接触各家的模型凭证。接下来要扩展的话方向有几个把 secretId/secretKey 换成短期 token 过期时间避免长期凭证泄露给不同工具配不同权限等级读操作和写操作分开校验把鉴权失败日志接到监控异常频率告警。这些都是在当前这层装饰器上叠加不用动业务函数。如果你还没生成 TaoToken 的 Key去控制台建一个把TAOTOKEN_API_KEY配进环境变量然后按第 4 节的 curl 和客户端各跑一遍成功/失败确认鉴权链路通了再往下做。接入文档里有 API 通道的详细说明模型对话页面可以直接验证 Key 是否可用。长期跑编码类 Agent 的话Coding Plan 那条通道更适合持续调用场景。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

你的目标是嵌入式开发 2026/9/28 21:10:08

你的目标是嵌入式开发

你好,王**,很高兴认识你。真的是一眨眼么,也不一定,毕业两年了对吧。为啥要学C语言呢?在原公司待着做一辈子的机械设计师不也是挺好的么?可靠的发展路线和依旧低微的工资是吧。对了,你是打算三个…

阅读更多 →
200美金/月的ChatGPT Pro真的值吗? 2026/9/28 21:10:08

200美金/月的ChatGPT Pro真的值吗?

200 美金一个月的 ChatGPT Pro,我觉得最容易让人产生误判的地方,就是很多人会下意识拿它和 Plus 比“回答质量”。如果只是问问题、改改文章、翻译、偶尔让 ChatGPT 帮忙分析一下东西,那 Pro 很难给你一种“贵了十倍,所以聪明了十…

阅读更多 →
Eclipse Mosquitto 的 Snap 包安装、测试与配置完整指南 2026/9/28 21:10:08

Eclipse Mosquitto 的 Snap 包安装、测试与配置完整指南

物联网消息队列后端 【免费下载链接】mosquitto Eclipse Mosquitto - An open source MQTT broker 项目地址: https://gitcode.com/gh_mirrors/mosquit/mosquitto 点击查看 免费下载 Snap 是 Linux 发行版上分发与运行 Mosquitto MQTT 代理(broker&…

阅读更多 →
关于Java的初步认识以及与C语言的比较 2026/9/28 21:10:08

关于Java的初步认识以及与C语言的比较

前言Java 是一门面向对象、跨平台、安全稳定的高级编程语言,由 Sun Microsystems(后被 Oracle 收购)于 1995 年推出,凭借 “一次编写,到处运行” 的特性,成为企业级开发、移动开发、后端服务等领域的主流语…

阅读更多 →
图解 vLLM:从 Prefill 到 Decode,看懂 vLLM 的优化 2026/9/28 21:10:08

图解 vLLM:从 Prefill 到 Decode,看懂 vLLM 的优化

简介 在上一篇中,我们沿着一次模型调用,认识了输入处理、Prefill、KV Cache、Decode 和输出返回等环节。对于普通的自回归生成,模型需要先处理已有输入,再利用缓存逐步预测后续 token,最后由服务将生成结果转换成文本…

阅读更多 →
用OpenCV部署YOLOP:CPU也能实现驾驶感知三合一 2026/9/28 21:10:01

用OpenCV部署YOLOP:CPU也能实现驾驶感知三合一

简介:一套基于OpenCV DNN模块部署YOLOP全景驾驶感知网络的完整工程,面向自动驾驶、智能交通方向的CV开发者与嵌入式部署工程师。YOLOP可同时完成交通目标检测、可驾驶区域分割与车道线检测三项视觉感知任务,适用车载视觉感知、行车记录仪后处…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉