GEOZ

一个环境变量切换六个 Iceberg 目录:MCP 服务器实测与踩坑记录

2026/9/22
一个环境变量切换六个 Iceberg 目录:MCP 服务器实测与踩坑记录

AIAI Summary (BLUF)

本文提供了一个逐步指南,教你如何设置一个 MCP 服务器来连接 Apache Iceberg 表,并依次测试七种 Iceberg REST 目录。服务器提供四个只读工具,通过一个环境变量决定读取哪个目录。

核心洞察

这篇文章最有意思的点是:同一个 MCP 服务器代码,不改一行,只换一个环境变量,就能对接六个不同的 Iceberg REST 目录。作者把每个目录的登录方式、存储依赖、实际返回结果都跑了一遍,还踩了几个坑。如果你正在做多 catalog 的数据工具,这篇的实测数据能帮你省不少试错时间。


这篇文章手把手带你搭一个 MCP 服务器,用来读取 Apache Iceberg 表,依次对接七个 Iceberg REST 目录。服务器提供四个只读工具,一个环境变量决定它读哪个目录。

项目地址:

https://github.com/xbill9/lakehouse-iceberg-2026


核心结论

  1. 同一个 MCP 服务器代码无需修改,仅通过切换 ICEBERG_CATALOG 环境变量和 catalogs.yaml 配置,即可对接 Apache Polaris、Google BigLake、Microsoft OneLake、AWS Glue、AWS S3 Tables 和 Snowflake Horizon 六个 Iceberg REST 目录。

  2. 四个只读工具(iceberg_list_tablesiceberg_describe_tableiceberg_count_rowsiceberg_scan_table)在全部六个目录上均无错误响应,全部可用。

  3. 扫描操作需要三个 pyiceberg 默认未安装的包:OneLake 需要 adlfs,S3 Tables 需要 s3fs,使用 aws login 会话时需要 botocore[crt];缺少这些包时元数据工具仍可用,但扫描会失败。

  4. 在非 Azure 环境下,DefaultAzureCredential 获取 token 耗时 553.1 秒,而 AzureCliCredential 仅需 0.6 秒;通过 MCP 服务器执行一次三行 OneLake 扫描,前者耗时 858.5 秒,后者仅 3.4 秒。

  5. Snowflake Horizon 在加载表时会主动返回 S3 存储凭证(包括 access-key-id、secret-access-key 和 session-token),即使目录配置中未要求,相关日志需要脱敏处理。

这个项目想做什么

一个 MCP 服务器给任何 MCP 客户端(比如 Claude Code 或其他编码代理)提供一组固定工具。这个服务器有四个,全是读操作:

  • iceberg_list_tables — 列出目录中所有 namespace.table
  • iceberg_describe_table — 列信息、分区、快照和元数据位置
  • iceberg_count_rows — 从快照摘要中获取精确行数
  • iceberg_scan_table — 读取少量行,附带精确计数、最小值和最大值

所有 Iceberg REST 目录都说同一套协议,所以理论上同一个服务器应该能对接所有目录。这个项目拿七个目录来验证:

  • Apache Polaris 1.7.0,本机 Docker 运行
  • Google BigLakeMicrosoft OneLakeAWS GlueAWS S3 TablesSnowflake Horizon,走公网
  • Databricks Unity,没跑,试用账号到期了

服务器直接通过 MCP 调用,中间没有 AI 模型,所以每个结果只取决于目录本身和服务器的配置。测量时间:2026-09-18 和 2026-09-19(UTC),每个目录跑一次,只读。


从哪里开始

策略是逐步推进。

先搞本地 Polaris 目录,只需要 Docker。然后跑 MCP 服务器,直接调用检查四个工具。接着一个一个加目录:登录方式、存储包、跑同样的四次调用。


开始之前你需要准备

  • Docker,用于本地 Polaris 目录
  • Python 3.10+ 和 pyiceberg 0.12.0 — 这次用的是 Python 3.14.7
  • 每个要对接的托管目录,需要一个有表的账号和可用的命令行登录:gcloudazaws 或 Snowflake 密钥对

第一步 — 启动 Polaris

$ git clone https://github.com/xbill9/lakehouse-iceberg-2026
$ cd lakehouse-iceberg-2026/iceberg-conformance
$ ./polaris-up.sh
$ export POLARIS_CLIENT_ID=root POLARIS_CLIENT_SECRET=s3cr3t
$ python3 seed_table.py --catalog apache-polaris

这会创建 probe_ns.probe_table:11 行,按天分区,四个快照。


第二步 — 看看服务器

服务器代码在 iceberg-mcp-hosts/servers/iceberg_mcp.py。它通过 stdio 用换行分隔的 JSON-RPC 说 MCP 协议,响应 initializetools/listtools/callping,没有用 MCP SDK。

它从两个环境变量决定读哪个目录:

$ ICEBERG_CATALOG=aws-glue ICEBERG_CATALOGS_FILE=catalogs.yaml python3 servers/iceberg_mcp.py

catalogs.yaml 里每个目录一条记录:URL、warehouse 和登录方式。服务器代码对所有目录都一样。


第三步 — 不用模型直接调用

sweep_catalogs.py 对每个目录启动一次服务器,每次都发同样的 MCP 调用:initializetools/list,然后对列出的第一个表跑四个工具。

$ cd ../iceberg-mcp-hosts
$ python3 sweep_catalogs.py --only apache-polaris
apache-polaris       probe_ns.probe_table                 list_tables=ok(1.0s)  describe_table=ok(0.4s)  count_rows=ok(0.0s)  scan_table=ok(0.1s)

扫描的返回结果展示了客户端收到什么:

id | ts | payload | region
0 | 2026-09-01 00:00:00+00:00 | row-0-0 | None
2 | 2026-09-01 02:00:00+00:00 | row-0-2 | None
3 | 2026-09-01 03:00:00+00:00 | row-0-3 | None

3 of 11 row(s) shown, read from snapshot-id 1196292829914853564
COUNT: exactly 11 row(s) are in the table in snapshot-id 1196292829914853564. Exact, over the whole table.
MIN and MAX of id over those 11 row(s): 0 and 23. Exact.

目录调用失败时返回以 CATALOG ERROR 开头的文本,这样代理仍然能说出它读不到什么。扫描脚本把这种文本算作失败。


第四步 — 每个目录加一条记录

每个托管目录需要在 catalogs.yaml 里有自己的登录配置。服务器据此构建 PyIcebergRestCatalog

目录 登录方式 文件读取方式
Polaris OAuth2 客户端 ID 和密钥 PyArrow,本地 file:
BigLake gcloud token,x-goog-user-project PyArrow,gs://
OneLake az token fsspec + adlfsabfss://
Glue SigV4,服务名 glue PyArrow,s3://,本地 AWS 登录
S3 Tables SigV4,服务名 s3tables fsspec + s3fs,目录颁发的凭证
Horizon Snowflake 密钥对 JWT PyArrow,s3://,目录颁发的凭证

S3 Tables 在收到 X-Iceberg-Access-Delegation: vended-credentials 头时颁发存储凭证。Horizon 不问自给。


第五步 — 安装存储包

列表、描述和计数只读目录元数据,所以只用 pyiceberg 就能跑。扫描要读数据文件,有三个目录需要 pyiceberg 默认不装的包。没有这些包,服务器能响应四个工具中的三个,扫描会失败:

CATALOG ERROR while scanning dbo.probe_table: ModuleNotFoundError: No module named 'adlfs'.

CATALOG ERROR while scanning probe_ns.probe_table: ModuleNotFoundError: No module named 's3fs'.

用新版 aws login 命令做的 AWS 登录,还需要额外一个包才能正常工作:

CATALOG ERROR while listing tables: MissingDependencyException: Missing Dependency: Using the login credential provider requires an additional dependency. You will need to pip install "botocore[crt]" before proceeding.
$ pip install adlfs s3fs "botocore[crt]"
用途
adlfs OneLake 扫描
s3fs S3 Tables 扫描
botocore[crt] 使用 aws login 会话时的 Glue 和 S3 Tables

提示:OneLake 直接用 az 凭证

OneLake 的数据文件需要 Azure 存储凭证,不只是目录 token。DefaultAzureCredential 是常见选择,它会先尝试 Azure VM 元数据服务,再去试 az 登录。不在 Azure 上时,这个尝试会一直等到重试耗尽:

     917ms No environment configuration found.
     921ms ManagedIdentityCredential will use IMDS
  553989ms DefaultAzureCredential acquired a token from AzureCliCredential
AzureCliCredential      0.6s
DefaultAzureCredential  553.1s

通过 MCP 服务器,一次三行的 OneLake 扫描用 DefaultAzureCredential 花了 858.5 秒,用 AzureCliCredential 只花了 3.4 秒。其他工具调用不受影响,因为只有扫描会读文件。


第六步 — 跑全部六个

$ python3 sweep_catalogs.py --only apache-polaris,google-lakehouse,microsoft-onelake,aws-glue,aws-s3tables,snowflake-horizon
$ python3 sweep_catalogs.py --report-only
catalog             measured (UTC)        table                   rows  partitioned     seconds: list describe count scan
apache-polaris      2026-09-18T23:58:37Z  probe_ns.probe_table      11  ts_day          1.0 0.5 0.0 0.1
aws-glue            2026-09-18T23:58:51Z  probe_ns.probe_table      11  ts_day          2.1 0.7 0.2 1.5
aws-s3tables        2026-09-18T23:58:57Z  probe_ns.probe_table      11  ts_day          2.2 0.3 0.2 2.0
google-lakehouse    2026-09-19T00:11:14Z  probe_ns.probe_table      11  ts_day          10.1 1.6 0.5 3.6
microsoft-onelake   2026-09-18T23:58:45Z  dbo.probe_table            6  (unpartitioned) 2.5 0.2 0.2 3.2
snowflake-horizon   2026-09-18T23:59:02Z  PROBE_NS.PROBE_TABLE      12  ts_day          7.3 2.2 1.4 1.9

四个工具在全部六个目录上都能用。 每个目录的 tools/list 都返回同样的四个工具,没有调用报错。

每个目录的第一次调用还要构建客户端和获取登录 token,所以 list 那一列最慢。每个数字都是单次调用,时间只反映量级。


第七步 — 看看返回了什么

这些表是为之前一篇文章单独创建的,服务器报告每个目录里有什么:

  • OneLake 的命名空间叫 dboid 存为可选 int,未分区表有 6 行
  • Horizon 返回大写名称 PROBE_NS.PROBE_TABLE,有 12 行
  • 其他四个 持有同样的 11 行表,按天分区

客户端只需要用 iceberg_list_tables 返回的名称。服务器原样传回,所以 dbo 和大写名称不需要特殊处理。


提示:Horizon 不问自给存储凭证

加载 Horizon 表会在客户端的文件读取器上留下 S3 凭证,尽管目录配置里没有要求:

snowflake-horizon
  client.region
  py-io-impl
  s3.access-key-id
  s3.secret-access-key
  s3.session-token
  s3.session-token-expires-at-ms

Horizon 的目录配置没有提供存储登录,所以它的扫描用那个凭证读文件。这也意味着加载 Horizon 表会携带一个活跃的存储凭证,相关日志需要脱敏。


第八步 — 从 MCP 客户端使用

任何能启动 stdio 服务器的 MCP 客户端都能跑它。Claude Code 的话,项目 .mcp.json

{
  "mcpServers": {
    "iceberg": {
      "command": "python3",
      "args": ["iceberg-mcp-hosts/servers/iceberg_mcp.py"],
      "env": {
        "ICEBERG_CATALOG": "aws-glue",
        "ICEBERG_CATALOGS_FILE": "iceberg-conformance/catalogs.yaml"
      }
    }
  }
}

要对接其他目录,改 ICEBERG_CATALOG 就行。上面的测量数据是直接调用服务器,没有用这个文件。


对比总结

目录 四个工具 额外包 本机登录
Apache Polaris 🟢 OAuth2 客户端密钥
Google BigLake 🟢 gcloud
Microsoft OneLake 🟢 adlfs az
AWS Glue 🟢 aws login 时需要 botocore[crt] aws
AWS S3 Tables 🟢 s3fsaws login 时需要 botocore[crt] aws
Snowflake Horizon 🟢 密钥对
Databricks Unity 未运行

总结

这篇文章的目标是把一个 Iceberg MCP 服务器对接七个目录,记录每个目录需要什么。关键在于保持服务器代码不变,只改目录配置,直接调用工具,让目录和结果之间没有模型介入。结果如下:

  • 🟢 四个工具在全部六个运行的目录上都能用:Polaris、BigLake、OneLake、Glue、S3 Tables 和 Horizon
  • 🟢 目录之间唯一的区别是一个环境变量和 catalogs.yaml 里的一条记录
  • ⚠️ 需要三个 pyiceberg 默认之外的包:OneLake 需要 adlfs,S3 Tables 需要 s3fsaws login 会话需要 botocore[crt];没有它们,元数据工具能用,扫描会失败
  • ⚠️ DefaultAzureCredential 在非 Azure 环境花了 553.1 秒;AzureCliCredential 花了 0.6 秒
  • ⚠️ Horizon 在加载表时返回 S3 凭证,没有要求
  • ❌ Databricks Unity 没有运行,试用账号到期了

范围:iceberg_mcp.py 1.0.0,pyiceberg 0.12.0,pyarrow 25.0.1,s3fs 2026.9.0,adlfs 2026.8.0,botocore 1.43.75,azure-identity 1.25.3,Python 3.14.7。Apache Polaris 1.7.0 在 Docker 中运行,本地文件存储;托管目录从一台机器走公网访问,AWS 在 us-east-1。每个目录在 2026-09-18 和 2026-09-19(UTC)跑一次;Unity 未运行。只读。工具只检查是否无错误响应;不同目录的表不同,所以只报告数值不做对比。托管目录不报告版本。

用一个 Iceberg MCP 服务器对接七个目录的策略,通过逐步推进的方式得到了验证。


参考链接

常见问题(FAQ)

这个 MCP 服务器怎么切换不同的 Iceberg 目录?

不用改代码,只换一个环境变量即可。设置 ICEBERG_CATALOG 指定目录名,再用 ICEBERG_CATALOGS_FILE 指向 catalogs.yaml,服务器就按该记录读取对应目录。

MCP 服务器提供哪些只读工具?

共四个:iceberg_list_tables 列出所有表,iceberg_describe_table 查看列、分区与快照,iceberg_count_rows 从快照摘要取精确行数,iceberg_scan_table 读取少量行并附精确计数与最值。

扫描 Iceberg 表时为什么报缺少模块?

扫描要读数据文件,而 pyiceberg 默认不装 adlfss3fs 等存储包。缺包时列表、描述、计数仍可用,扫描会返回 CATALOG ERROR,需 pip install adlfs s3fs 等补齐依赖。

晓婷深圳
本文由 晓婷 审核,最后更新于 2026年9月22日
联系编辑 →
← 返回文章列表
分享到:微博

版权与免责声明:本文仅用于信息分享与交流,不构成任何形式的法律、投资、医疗或其他专业建议,也不构成对任何结果的承诺或保证。

文中提及的商标、品牌、Logo、产品名称及相关图片/素材,其权利归各自合法权利人所有。本站内容可能基于公开资料整理,亦可能使用 AI 辅助生成或润色;我们尽力确保准确与合规,但不保证完整性、时效性与适用性,请读者自行甄别并以官方信息为准。

若本文内容或素材涉嫌侵权、隐私不当或存在错误,请相关权利人/当事人联系本站,我们将及时核实并采取删除、修正或下架等处理措施。也请勿在评论或联系信息中提交身份证号、手机号、住址等个人敏感信息。