1. 自研 MCP 服务裸奔的真实场景
你写了一个 MCP 服务,本地跑通、工具调用正常,然后顺手把它挂到一台有公网 IP 的机器上,准备让 AI 工具连过来用。问题就出在这一步:MCP 服务默认没有鉴权层,任何知道地址和端口的人都能直接调用你的工具,读你的数据、触发你的操作。
MCP(模型上下文协议)本质上是给 AI 工具和外部能力之间搭的一条通道。通道本身不负责身份判断,它只负责把请求转发给对应的工具函数。所以当你把自研 MCP 服务暴露出去时,缺的不是协议实现,而是一层"你是谁、你能不能调"的校验。常见的风险有三类:未授权调用导致敏感数据被读走;伪造请求篡改参数;以及被脚本高频刷接口把资源打满。
这篇要解决的就是这件事:给自研 MCP 服务加一层统一 Key 鉴权,用 TaoToken 作为 Key 的签发与校验通道,在服务端配置文件里写入鉴权骨架,最后用一条带 Key 的 curl 命令验证整条链路跑通。适合已经在写 MCP 服务、但还没做鉴权的开发者,也适合想把多个自研工具统一收口到一套 Key 体系下的团队。下面从接入准备开始,一步步给出可复制的配置和验证命令。
2. TaoToken 前置:统一 Key 与 API 通道
在动手改服务端配置之前,先把 TaoToken 这边的接入点理清楚。TaoToken 在这里扮演的角色是统一 Key 的签发与校验入口,你的 MCP 服务不需要自己维护一套用户密码体系,只需要在请求进来时把 Key 交给校验通道确认有效性即可。
官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api (这个地址不加 UTM 参数,直接用于程序请求)。你需要先在控制台创建 API Key,控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,Key 管理页面在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。创建好之后把 Key 复制出来,后面写进服务端配置。
这里有个容易踩的点:Key 不要硬编码进源码,也不要提交到 Git。正确做法是写进配置文件或环境变量,配置文件本身加进 .gitignore。我试过把 Key 直接写在 Python 文件里,结果一次误提交就得全部轮换,很麻烦。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有 Key 的请求头格式和校验接口说明,配置前建议先扫一眼。
如果你后面还要做长期编码或 Agent 类的持续调用,可以了解下 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,它更适合高频、长周期的调用场景。单纯验证模型连通性的话,模型对话入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=model&utm_campaign=rewrite 。
3. 可复制配置:config.toml 鉴权骨架
现在进入核心部分。假设你的 MCP 服务用 Python 写,配置文件用 config.toml。下面这份骨架把鉴权相关的字段全部抽出来,你直接复制改值即可。
# config.toml [mcp] name = "my-knowledge-graph-mcp" host = "0.0.0.0" port = 8765 [auth] # 是否开启鉴权,调试阶段可临时关,上线必须为 true enabled = true # TaoToken API 基址,用于校验 Key 有效性 verify_endpoint = "https://taotoken.net/api" # 从控制台创建的 Key,建议用环境变量注入,这里演示直接写 api_key = "sk-你的TaoTokenKey" # 请求头里携带 Key 的字段名 header_name = "Authorization" # 请求头前缀,最终形如 "Bearer sk-xxx" header_prefix = "Bearer" # 校验超时(秒) timeout = 5 [logging] level = "INFO" # 记录每次调用的 Key 尾号和结果,便于排查异常 log_auth = true配置文件写好后,服务端读取逻辑大致是这样:请求进来先看auth.enabled,为 true 就从请求头取header_name指定的字段,去掉header_prefix前缀拿到 Key,然后带着这个 Key 去verify_endpoint校验。校验通过才进入 MCP 工具分发,否则直接返回 401。
下面是一段最小可用的服务端鉴权中间件示例,用 FastAPI 风格写,你可以按自己的框架改写:
# auth_middleware.py import os import httpx from fastapi import Request, HTTPException import tomllib with open("config.toml", "rb") as f: cfg = tomllib.load(f) AUTH = cfg["auth"] async def verify_key(raw_key: str) -> bool: if not AUTH["enabled"]: return True headers = {AUTH["header_name"]: f'{AUTH["header_prefix"]} {raw_key}'} try: async with httpx.AsyncClient(timeout=AUTH["timeout"]) as client: resp = await client.get( f'{AUTH["verify_endpoint"]}/models', headers=headers, ) return resp.status_code == 200 except httpx.RequestError: return False async def auth_guard(request: Request): if not AUTH["enabled"]: return header_val = request.headers.get(AUTH["header_name"], "") if not header_val.startswith(AUTH["header_prefix"]): raise HTTPException(status_code=401, detail="missing or malformed key") raw_key = header_val[len(AUTH["header_prefix"]):].strip() if not await verify_key(raw_key): raise HTTPException(status_code=401, detail="invalid key")把auth_guard挂到 MCP 服务的路由入口上,所有工具调用请求都会先过这一层。注意verify_endpoint后面拼的/models只是用来做一次轻量校验,实际以接入文档里给出的校验路径为准。Key 从环境变量注入的写法是api_key = os.environ.get("TAOTOKEN_KEY"),比写死在 toml 里更安全。
4. 验证请求:带 Key 的 curl 调用
配置写完,先别急着接 AI 工具,用 curl 手动打一次,确认鉴权链路是通的。先测不带 Key 的情况,应该被拦下来:
curl -i -X POST http://127.0.0.1:8765/mcp/tools/call \ -H "Content-Type: application/json" \ -d '{"tool":"query_graph","args":{"q":"test"}}'预期返回 401,body 里带missing or malformed key。这一步能过,说明鉴权中间件确实生效了,不是摆设。
再测带正确 Key 的情况:
curl -i -X POST http://127.0.0.1:8765/mcp/tools/call \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-你的TaoTokenKey" \ -d '{"tool":"query_graph","args":{"q":"test"}}'预期返回 200,并且 body 里是你 MCP 工具的正常返回结果。如果这一步返回 401,先检查 Key 有没有复制完整、前缀是不是Bearer(注意后面有个空格)、以及verify_endpoint是否可达。
再测一个错误 Key,确认校验逻辑不是"只要带了 Key 就放行":
curl -i -X POST http://127.0.0.1:8765/mcp/tools/call \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-wrong-key-123" \ -d '{"tool":"query_graph","args":{"q":"test"}}'预期返回 401,body 里带invalid key。三条命令跑完,鉴权链路就算验证通过了。成功的结果是:无 Key 被拒、正确 Key 放行、错误 Key 被拒,三种情况都符合预期。
5. 本篇常见错排查
实际配置时,报错基本集中在这几个地方,对照排查能省不少时间。
第一个是 401 一直不消失,但 Key 明明是对的。大概率是请求头前缀没对齐,Bearer和 Key 之间必须有一个空格,少空格或者多空格都会导致解析失败。另一个可能是verify_endpoint写成了带 UTM 的官网地址,校验接口应该用 https://taotoken.net/api 这个基址,不要拼官网的推广参数。
第二个是服务启动就报配置文件读取失败。tomllib在 Python 3.11 才进标准库,低版本要么升级,要么用tomli替代。另外 toml 里字符串必须用双引号,单引号在某些解析器下会出问题。
第三个是校验请求超时。timeout设太短,网络抖动就会误判为无效 Key。建议设 5 秒起步,同时在校验失败时区分"网络错误"和"Key 无效",前者可以重试,后者直接拒绝,不要混在一起返回同一个错误码。
第四个是日志里看不到 Key 尾号,排查时不知道是哪个客户端在调。检查log_auth是否为 true,以及日志逻辑里有没有把 Key 截断后再打印。完整 Key 不要进日志,只留尾号 4 位即可。
第五个是把auth.enabled设成 false 之后忘了改回来。调试阶段临时关闭可以,但上线前一定要确认它是 true,否则等于没加鉴权。建议在启动日志里打印一行auth enabled: true/false,一眼就能看到。
6. 接入文档与后续调用入口
鉴权跑通之后,下一步就是把它接到实际的 AI 工具或 Agent 里。接入细节、请求头格式、校验接口的完整说明都在接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,配置前建议完整过一遍。Key 的创建和管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,需要轮换或新增 Key 时从这里操作。
如果你只是想先确认模型侧能不能正常对话,用模型对话入口快速验证:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=model&utm_campaign=rewrite 。而如果你的 MCP 服务是要长期挂在 Agent 里被高频调用的,Coding Plan 会更合适:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。
最后补一个实用技巧:把 curl 验证命令写成一个 shell 脚本,每次改完配置先跑一遍三条命令,确认无 Key 被拒、正确 Key 放行、错误 Key 被拒,再去做上层接入。这样能把鉴权问题和业务逻辑问题分开,排查时不会互相干扰。