Google Cloud Run部署的Python MCP工具调用403错误排查及调用咨询
一、403错误可能原因及解决方法
受众(Audience)URL不匹配
确保获取ID令牌时使用的audience与Cloud Run服务的URL完全一致,不能有多余的斜杠,必须包含https://前缀。例如,如果服务URL是https://my-service-abc123.a.run.app,那么audience必须严格等于这个值,不能是https://my-service-abc123.a.run.app/(末尾带斜杠)。服务账号权限配置错误
确认服务账号已被授予Cloud Run Invoker(roles/run.invoker)权限,且该权限是直接绑定到目标Cloud Run服务上的,而非仅在项目级别。可通过Cloud Console进入Cloud Run服务的权限页面,检查是否有该服务账号的roles/run.invoker角色。凭证文件路径问题
在Windows系统中,路径中的反斜杠需要正确处理。建议使用原始字符串(r'path\to\file.json')或替换为正斜杠('path/to/file.json'),避免因转义问题导致凭证文件无法加载。令牌传递格式错误
确保Authorization头的格式是Bearer <TOKEN>,注意Bearer后面有一个空格。检查代码中是否正确拼接了令牌,没有多余的空格或字符。Cloud Run入口设置限制
如果Cloud Run服务的Ingress设置为"Internal only"或"Internal and Cloud Load Balancing",即使服务账号有权限,外部请求也会被拒绝。确认Ingress设置为"Allow all traffic"。
二、调用特定MCP工具的请求体格式
使用FastMCP的streamable-http传输时,调用工具的POST请求体需要遵循MCP规范,包含tool_name和parameters字段。以你的add工具为例,请求体格式如下:
{ "tool_name": "add", "parameters": { "a": 5, "b": 3 } }
修正后的调用代码示例
结合正确的请求体和权限排查,修正后的调用代码如下:
import os import json import requests import google.oauth2.id_token import google.auth.transport.requests def runCloudFunction(): # 使用原始字符串或正斜杠处理Windows路径 os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = r'path\to\file.json' request = google.auth.transport.requests.Request() # 确保audience与Cloud Run服务URL完全一致,无末尾斜杠 audience = 'https://cloud_run_service_url' TOKEN = google.oauth2.id_token.fetch_id_token(request, audience) # 构造调用add工具的请求体 payload = { "tool_name": "add", "parameters": { "a": 10, "b": 20 } } r = requests.post( f"{audience}/mcp", headers={ 'Authorization': f"Bearer {TOKEN}", 'Content-Type': 'application/json' }, json=payload # 使用json参数自动处理序列化 ) print(f"状态码: {r.status_code}") print(f"响应内容: {r.text}") if __name__ == "__main__": runCloudFunction()
额外验证步骤
使用
gcloud命令测试访问:gcloud run services invoke <service-name> --region <region> --method POST --path /mcp --body '{"tool_name":"add","parameters":{"a":1,"b":2}}'如果此命令成功,说明服务本身配置正确,问题出在代码中的令牌获取或传递环节。
解码ID令牌验证受众:
可以使用pyjwt库检查令牌的aud字段是否与服务URL一致:import jwt decoded = jwt.decode(TOKEN, options={"verify_signature": False}) print(decoded['aud'])
内容的提问来源于stack exchange,提问作者Sachu

