Codex 跑 QGIS MCP 工具:Key 用 TaoToken
2026/9/14 23:45:30 网站建设 项目流程

1. Codex Desktop 要调 QGIS,为什么先卡在模型 Key 上

Codex Desktop 想读当前 QGIS 工程里的图层列表,本身不是难事:QGIS MCP 插件把工程信息暴露在本地 socket 上,Codex 只认 MCP STDIO,所以需要codex-qgis-bridge.py在中间转换协议。可很多人在原方案里走到最后一步才发现,bridge 起来了、工具也发现了、config.toml 也没写错,模型推理却因为官方 Key 的额度限制一直转圈报错。这件事的解法很简单:把模型侧切到 TaoToken,拿 Key 的入口是完整官网链接 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 。它不是替代 QGIS 插件,也不是替代桥接脚本,只是让 Codex 在调用mcp__qgis.get_layers这类工具时,背后有一个稳定可用的兼容通道。

原方案把重点放在协议桥接上,这没有错。但「Codex 能发现工具」和「Codex 能真正跑完一次工具调用」是两回事。工具发现依赖 MCP 配置,工具执行依赖模型端点。QGIS 的图层列表、坐标系、要素类型这些信息要经过 PyQGIS 读出来,再交给 Codex 组织成自然语言回答,模型这一环必须有可用的 API Key。以前在 Codex 里直接填 Anthropic 官方 Key,经常遇到额度耗尽、新账号无法开通、多项目共用一把 Key 不知道谁在用等情况。换成 TaoToken 之后,注册、创建 Key、填 Base URL 三步就能解决,下面按原方案的目录完整走一遍。

2. 整体链路:socket 协议和 MCP 协议之间为什么需要翻译

先看清楚这条链路再动手,后面排障会省很多时间。原方案里 Codex 并不直接访问 QGIS MCP 插件,实际链路是:

Codex Desktop ->codex-qgis-bridge.py(MCP STDIO Server)-> QGIS MCP Plugin(TCP socket)-> PyQGIS API

两个关键差异决定了必须有桥接脚本:QGIS MCP 插件默认监听127.0.0.1:9876,协议是「4 字节大端长度前缀 + JSON 命令」;而 Codex 需要的是 MCP STDIO Server,也就是从标准输入读 JSON-RPC、往标准输出写结果。两边协议不同,不能直接用 URL 或 HTTP 请求打通。如果试图用浏览器访问http://127.0.0.1:9876,失败是正常的,因为那个端口根本不是 HTTP 服务。

桥接脚本的角色是把自己伪装成一个标准 MCP Server,把 Codex 发来的工具调用转换成 QGIS 插件认识的 socket 命令,再把 QGIS 返回的 JSON 包装成 MCP 工具结果。它至少需要暴露 4 个工具:检查桥接是否连得上 QGIS 的diagnose、获取图层摘要的get_layers、拿全部图层名的get_layer_names、按名称搜索图层的find_layer。只要这条链路通,Codex 就能在对话里拿到类似「天地图-矢量地图 / raster / EPSG:3857」这样的返回,后续做 GIS 自动化才有基础。

3. 环境准备:TaoToken 的 Key、QGIS 插件和 Python MCP SDK

3.1 注册并创建 API Key

在动手写桥接脚本之前,先解决模型侧的身份问题。打开 TaoToken 注册并登录,在控制台创建 API Key,创建后把密钥复制到本地临时文件。注意区分两个地址:注册、创建 Key、看模型广场和用量走官网落地页 https://taotoken.net/?utm_source=taotoken_aicg_blog_end ;真正填进 Codex 配置文件的 Base URL 是接口地址https://taotoken.net/api,末尾不要加/v1。这两个东西混在一起是最常见的配置错误,后面会专门说。

创建 Key 时不用纠结选哪个模型,模型 ID 以后可以随时换。在 Codex 的config.toml里,模型名最终以 TaoToken 模型广场当时列出的 ID 为准,不要照着别的文章写一个已经下线的名字。先用一把刚创建的YOUR_API_KEY把整条链路跑通,确认调用成功后再去模型广场对比哪个模型更适合自己的 GIS 场景。

3.2 QGIS 侧安装插件并确认端口

在 QGIS 3.40 LTR 里安装 QGIS MCP 插件,启用后在菜单栏可以看到「插件 -> QGIS MCP -> MCP :9876」。点击启动后,用 PowerShell 检查端口是否在监听:

Get-NetTCPConnection -LocalPort 9876

正常情况下会看到类似下面的输出,OwningProcessqgis-ltr-bin.exe

LocalAddress LocalPort State OwningProcess 127.0.0.1 9876 Listen qgis-ltr-bin.exe

看到这一行,说明 QGIS 插件端已经就绪。如果端口被其他程序占用,在插件菜单里换成9877之类的端口,后面 Codex 配置里的QGIS_MCP_PORT也要同步改。

3.3 安装 Python MCP SDK

桥接脚本依赖 FastMCP,先装好 Python MCP SDK:

python -m pip install mcp

装完验证一下能否正常导入:

python -c "from mcp.server.fastmcp import FastMCP; print(FastMCP)"

能打印出类信息就说明 SDK 可用。这里建议把 Python 的完整路径记住,后面 Codex 配置里要直接写绝对路径。Windows 上如果同时装了多个 Python,Codex 启动时可能拿到错误版本,导致 bridge 脚本 import 失败,所以绝对路径比command = "python"更稳妥。

4. 编写 codex-qgis-bridge.py 桥接脚本

4.1 桥接脚本的核心逻辑

脚本要做的三件事:读取环境变量里的 QGIS socket 地址;用带长度前缀的 JSON 协议向 QGIS 插件发命令;把返回结果包装成 FastMCP 工具。下面是一个比原方案更完整的版本,额外加了--probe参数,方便不经过 Codex 单独验证脚本到 QGIS 插件这段链路是否通。

import json import os import socket import struct import sys from typing import Any from mcp.server.fastmcp import FastMCP HEADER = struct.Struct(">I") CLIENT_VERSION = "codex-qgis-bridge-0.2.0" mcp = FastMCP("qgis") def _qgis_addr(): host = os.getenv("QGIS_MCP_HOST", "127.0.0.1") port = int(os.getenv("QGIS_MCP_PORT", "9876")) return host, port def _recv_exact(sock: socket.socket, size: int) -> bytes: chunks = bytearray() while len(chunks) < size: chunk = sock.recv(size - len(chunks)) if not chunk: raise ConnectionError("QGIS MCP socket closed before response was complete") chunks.extend(chunk) return bytes(chunks) def qgis_command(command_type: str, params: dict | None = None): payload = { "type": command_type, "params": params or {}, "client_version": CLIENT_VERSION, "client_install": "source", "client_root": os.path.abspath(__file__), } token = os.getenv("QGIS_MCP_TOKEN", "").strip() if token: payload["token"] = token message = json.dumps(payload, ensure_ascii=False).encode("utf-8") host, port = _qgis_addr() with socket.create_connection((host, port), timeout=15) as sock: sock.sendall(HEADER.pack(len(message)) + message) response_size = HEADER.unpack(_recv_exact(sock, HEADER.size))[0] response = json.loads(_recv_exact(sock, response_size).decode("utf-8")) if response.get("status") != "success": raise RuntimeError(response.get("message", response)) return response.get("result") @mcp.tool() def diagnose() -> dict[str, Any]: """Return basic QGIS MCP bridge diagnostics.""" host, port = _qgis_addr() result = qgis_command("get_layers", {"limit": 1, "offset": 0}) return { "qgis_host": host, "qgis_port": port, "connected": True, "layer_count": result.get("total_count"), } @mcp.tool() def get_layers(limit: int = 500, offset: int = 0) -> dict[str, Any]: """Return layers from the current QGIS project with metadata.""" return qgis_command("get_layers", {"limit": limit, "offset": offset}) @mcp.tool() def find_layer(name_pattern: str) -> dict[str, Any]: """Find QGIS project layers by name pattern.""" return qgis_command("find_layer", {"name_pattern": name_pattern}) @mcp.tool() def get_layer_names(limit: int = 500, offset: int = 0) -> list[str]: """Return all layer names from the current QGIS project.""" result = qgis_command("get_layers", {"limit": limit, "offset": offset}) return [layer["name"] for layer in result.get("layers", [])] if __name__ == "__main__": if "--probe" in sys.argv: print(json.dumps(qgis_command("get_layers", {"limit": 10}), ensure_ascii=False)) else: mcp.run()

把这个脚本存成E:\work\codex-qgis-bridge.py。注意get_layer_names并没有直接发一个get_layer_names命令给 QGIS,而是先调get_layers再从结果里提取名字,这样插件端只要实现一个取图层的命令就行,桥接层负责组织返回格式。这个设计尽量减少了插件端命令数量,逻辑也更集中。

4.2 先单独验证脚本能连上 QGIS

在终端里执行:

python E:\work\codex-qgis-bridge.py --probe

能返回 JSON 数组,说明 Python bridge 到 QGIS MCP Plugin 再到 QGIS 工程这段已经通了。返回结果里应该能看到图层名、类型和坐标系之类的字段。这一步不需要 Codex 参与,是排查问题时很好用的探针。

5. Codex 配置:MCP 桥接和模型 Provider 一起写进 config.toml

打开C:\Users\Administrator\.codex\config.toml,按下面的内容补全。其中[mcp_servers.qgis]和原方案保持一致,桥接脚本路径改用你实际存放的位置;模型 Provider 部分是新加的,Key 从 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 创建。

model = "taotoken/YOUR_MODEL_ID" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat" [mcp_servers.qgis] command = "D:/Python/python.exe" args = ["E:/work/codex-qgis-bridge.py"] env = { QGIS_MCP_HOST = "127.0.0.1", QGIS_MCP_PORT = "9876" } startup_timeout_sec = 120

这里有几个容易搞混的细节。base_url填的是接口地址https://taotoken.net/api,不是官网首页,也不是带/v1的地址;env_key是 Codex 去环境变量里读取 Key 的变量名,写到config.toml里只需声明这个名字,然后在系统环境变量里设置TAOTOKEN_API_KEY为你在 TaoToken 控制台创建的那串密钥;YOUR_MODEL_ID是占位符,实际值要去 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 的模型广场看当时上架了哪些模型,不同日期可用模型列表可能不一样,照着最新列表填。

配置保存后,重启 Codex Desktop,或者新开一个会话。MCP 工具和模型 Provider 都是在会话启动时读取的,老会话不会自动刷新。如果之前 Codex 一直开着,直接关掉重开,不要只切换对话窗口。

6. 联动验证:按顺序检查端口、bridge 和模型调用

6.1 验证 QGIS 插件端和桥接脚本

先确认 QGIS 插件还在监听9876

Get-NetTCPConnection -LocalPort 9876

再跑一次探针命令:

python E:\work\codex-qgis-bridge.py --probe

两层都通过后,打开 Codex,在对话里输入一句「检查 qgis bridge 状态」。如果 Codex 返回类似「connected: true, layer_count: 5」的信息,说明工具注册和模型调用都正常。这里要特别留意一个现象:Codex 能列出工具列表,和 Codex 真正执行一次工具,是两个阶段。前者只依赖 MCP 配置,后者还要经过模型端。如果前面 Key 配置有问题,工具列表会出现,但一调用就报模型错误。

6.2 让 Codex 读取 QGIS 图层

在 Codex 对话里输入:

通过 QGIS MCP 服务,获取当前 QGIS 工程所有图层名称,并显示坐标系

Codex 会调用mcp__qgis.get_layersmcp__qgis.get_layer_names,然后把返回拼成表格。正常结果类似:

天地图-矢量地图 raster EPSG:3857 test_point vector EPSG:4490

如果这一步成功,说明 Codex Desktop、桥接脚本、QGIS 插件、PyQGIS 这四层完全打通,剩下的 GIS 自动化就可以在这个底座上继续搭。

7. 常见问题:协议问题、配置问题和模型问题要分开排查

7.1 Codex 工具列表里看不到 qgis 相关工具

优先检查这几项:config.toml是否放在了当前用户目录C:\Users\<用户名>\.codex下;command里的 Python 路径是否存在,args里的桥接脚本路径是否写对;QGIS MCP 插件是否在 QGIS 菜单里启动;Codex 是不是配置修改后完全重启过。还有一个容易忽略的点:config.toml里如果同时存在多个[mcp_servers.xxx],每个工具名必须唯一,重名会互相覆盖。

7.2 浏览器访问 http://127.0.0.1:9876 失败

这是正常现象。QGIS MCP 插件用的是带长度前缀的 socket 协议,不是 HTTP。浏览器和Invoke-WebRequest都是 HTTP 客户端,当然访问不了。这类问题不要浪费时间去排查,直接用桥接脚本的--probe参数验证。

7.3 工具能找到,但调用时报模型相关错误

这种情况和 MCP 桥接无关,问题出在模型这一侧。先在系统环境变量里确认TAOTOKEN_API_KEY已经设置,并且 Codex 重启后能读到;再去 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 的模型广场确认填进config.toml的模型 ID 还没下架;最后用curl或直接在 TaoToken 的模型对话页面里用同一把 Key 发一条消息,看 Key 是否有效。TaoToken 的模型对话入口在官网控制台里,和 Key 管理在同一个登录体系下,顺手还能看这次调用有没有被记录。

7.4 中文图层名在终端里乱码

这是 Windows 终端编码问题,不是数据问题。QGIS 返回的 JSON 本身是 UTF-8,Codex 对话窗口一般能正常显示。在调试脚本里如果打印到终端乱码,可以临时转一下:

layer["name"].encode("unicode_escape").decode("ascii")

8. 安全边界:只监听本机,不要暴露到公网

QGIS MCP 插件的能力边界是「读取当前工程信息」,但 PyQGIS 背后能做的事远比这多。原方案里的安全建议要保留:插件只监听127.0.0.1,不要改成0.0.0.0,也不要通过端口转发暴露到局域网或公网;如果和外部工具对接,启动QGIS_MCP_TOKEN,在桥接脚本的环境变量env里补上对应的 token 值;桥接脚本只暴露必要工具,涉及删除图层、写文件、执行代码的能力要谨慎开放。Codex 可能在对话过程中生成一段 PyQGIS 脚本让你本地执行,这段脚本该由你自己在 QGIS Python 控制台里跑完,再把输出贴回 Codex,而不是让 Codex 远程执行。

整套配置跑通之后,如果只是偶尔试试,用免费额度就够;如果准备把 QGIS 自动化当成日常工作流,建议打开 Coding Plan 看看套餐和当前用量是否匹配。Key 的管理和用量记录都在 控制台 API Keys,第一次调用成功之后就去那里确认一下这次get_layers调用是否记上了账,顺便把模型 ID 和 Base URL 的对应关系再核对一遍。以后要换模型、换桥接脚本、换 QGIS 工程,都不用改插件端口,只需要动config.toml里的 provider 配置,这点是这套方案最省心的地方。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询