mcpField
M
mcportal

mcportal

本地服务
github.com/yonghwan1106法律与政务011 周前更新

关于 mcportal

mcportal 将韩国 data.go.kr 公共 API 规范转换为 OpenAPI 3.1 和 MCP 服务,适合需要自托管并控制 API 用量的用户。预设支持回放响应,可在开发时免密使用。

主要功能

  1. 01将 API 规范规范化为 OpenAPI 3.1
  2. 02把规范编译为 MCP 服务
  3. 03通过退避和缓存限制每日调用预算
  4. 04无需 API 密钥回放已记录响应
  5. 05通过命令行查看预设和配额状态

适用场景

  1. 01通过 MCP stdio 提供预设 API 服务
  2. 02无需凭据复现 API 响应和测试
  3. 03按每日预算限制 data.go.kr 实时调用
  4. 04将 API 规范转换为 MCP 服务

MCP 相关

可选的 MCP 层通过 stdio 向 MCP 客户端提供编译后的 data.go.kr API 服务包。支持免密回放模式,也可在预算保护下进行实时调用。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"yonghwan1106-mcportal": {
"command": "uvx",
"args": [
"mcportal"
]
}
}
}
启动命令uvx mcportal

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypimcportal0.2.2

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

MCPortal

连接 data.go.kr 与全球 MCP 生态系统的缺失桥梁。 MCPortal 将韩国公共 API 规范标准化为 OpenAPI 3.1,再将其编译为 MCP 服务,并对所有出站调用 施加严格的每日预算限制,同时提供退避和缓存机制。完全开源,完全可自托管。

한국어 문서: README.ko.md — 本英文 README 是规范文档,韩文文件 是其译文。

准确性免责声明

data.go.kr 不提供“剩余配额”接口。因此,MCPortal 的使用记录仅统计经由 MCPortal 发出的调用,所以只是尽力而为的估算。同一 serviceKey 在 MCPortal 之外消耗的调用 ——例如其他脚本、门户控制台或其他工具发出的调用——都不会计入记录,因此记录中的数值 始终是实际用量的下限估算。可信赖的并非该估算,而是硬性预算上限(CALL_BUDGET)。 即使记录有误,硬性保护机制也会在超过每日上限时实际阻止调用,因此防止因配额问题导致 账户受处罚的安全保障始终来自 CALL_BUDGET。

无密钥边界

无需任何 API 密钥即可使用需要 data.go.kr 服务密钥
重放 record/replay 磁带调用实时 API
完整测试套件(pytest),包括在合成传输上运行的 record 模式测试采样实时响应
根据已提交的规范和磁带启动 MCP 服务(mcportal serve <id> --replay)——可从仓库检出目录启动,也可将 PyPI 安装指向该目录启动(注 1)临时转换尚无磁带的 API
重新生成编译演示(examples/compile_demo.py)录制新磁带
重新生成并列出预设包(mcportal compile / mcportal presets)——自 0.2.0 起随 wheel 一同发布(注 1)采样仍未解析的响应架构(mcportal sample)
查看配额状态(mcportal quota status)依赖密钥的基准测试项目 K1–K3
运行基准测试工具包(benchmarks/PROTOCOL.md 中的五个免密项目)—

MCPortal 中的所有演示、开发和 CI 流程都无需密钥。record/replay 层会重放先前录制的磁带,因此无需提供 serviceKey,就能复现相同的响应流程并通过整个测试套件。规范到 MCP 的转换已在构建时完成——编译后的构件已提交到 specs/ 下——因此克隆仓库,甚至启动 MCP 服务并响应工具调用,都无需密钥:mcportal serve 15000115 --replay 可通过 stdio 提供八个工具,且环境中无需出现 serviceKey。

这里特意写了克隆。重放需要磁带,而磁带是上游响应的录制文件,只保留在仓库中,不会随 wheel 一同发布(注 1);因此,单纯执行 pip install mcportal 后没有可供重放的内容,除非通过 --presets-root <path> 或 MCPORTAL_PRESETS=<path> 将其指向一个检出目录。四个预设包中有三个存在磁带——仓库中也没有 15081808 的磁带,因为它被有意排除在采样之外(见下文预设),所以该预设包只能实时使用。

需要密钥的场景范围很窄:向 data.go.kr 发送实时请求,以及采样或转换尚无磁带的 API。

注 1——wheel 包含和不包含的内容。 自 0.2.0 起,发布的 wheel 包含四个预设包(16 个包文件以及 presets/ 下的两个文档,以构建后的构件为准),因此,直接执行 pip install mcportal 后,无需检出目录即可运行 mcportal presets。 自 0.2.2 起,wheel 还包含三个 sampled_schemas.json 文件——15000115、15101612 和 15102108;15081808 没有此文件,因为它被有意排除在采样之外。这些文件随包发布是因为 mcportal compile --check 需要它们:--check 不会对文件进行差异比较,而是根据源文件、整理层和采样层重新合成 openapi.json;因此,如果没有采样层,已安装的副本就无法重现其随包发布的 openapi.json。在发布的 0.2.1 版本中,从普通安装运行 compile --check 会报告 4 个中有 3 个存在偏差,并以状态码 3 退出(测量日期:2026-08-15);在该版本中,请传入 --presets-root <checkout>/presets。0.2.2 则恢复为 4 个全部匹配,状态码为 0,仅凭安装包即可完成。 这三个文件仅包含从响应中推断出的字段名和类型——不含任何响应值——而这些结构已随 openapi.json 一同发布,因此不会暴露 wheel 原本未包含的信息。 wheel 仍不包含的是录制的上游流量本身:cassettes/ 和 samples/ 仅保留在仓库中,因此 mcportal serve --replay 只能通过检出目录运行。若要使用其他预设包集合,或为 PyPI 安装提供磁带,请通过 --presets-root <path> 或环境变量 MCPORTAL_PRESETS=<path> 将 MCPortal 指向一个检出目录。 如果 mcportal presets 未找到任何预设包,它会打印搜索过的路径。

无密钥复现指南
# 1) Compile a spec from synthetic fixtures (zero network, zero credentials).
python examples/compile_demo.py
#    -> specs/demo/openapi.json + specs/demo/samples/*.json
#    Re-running produces byte-identical output; the determinism check is
#    built into the script.

# 2) Stand up an MCP server over stdio from a committed bundle (no key).
#    Needs the [mcp] extra. --replay reads presets/<id>/cassettes/<id>.json,
#    which exists for 15000115, 15101612 and 15102108; 15081808 has none.
mcportal serve 15000115 --replay
#    -> 8 tools over stdio, zero credentials. On a PyPI install add
#    --presets-root <checkout>/presets (note 1).
#
#    The same thing without the CLI, against any spec plus any cassette:
python -c "from mcportal.mcp import build_server; \
build_server('specs/demo/openapi.json', mode='replay', \
cassette_path='<cassette path>')"
#    specs/demo/ commits the spec and the samples, not a cassette, so
#    <cassette path> must point at one you recorded yourself. That a server
#    really stands up and answers a tool call without a key is proven by the
#    last case in tests/test_mcp_wiring.py, which builds a synthetic cassette
#    in tmp_path. presets/<id>/openapi.json fits the same slot.

# 3) Regenerate the preset bundles from real published specs (no key).
#    Works from a checkout, and — since 0.2.2 started carrying the sampled
#    layer — from a plain pip install too (note 1). On 0.2.1 as published from
#    PyPI, or to point at a different bundle set, pass --presets-root <path>
#    or set MCPORTAL_PRESETS=<path>.
mcportal presets            # list
mcportal compile --check    # byte-compare committed vs regenerated (exit 3 on drift)

# 4) The whole test suite (no live network, no key, no real data).
pytest -q

安装

pip install mcportal            # core runtime (single dependency: httpx)
pip install "mcportal[mcp]"     # + the MCP conversion layer (fastmcp)

依赖策略:核心运行时只依赖 httpx。 规范化编译器(mcportal.compiler)仅使用标准库和 httpx。fastmcp 仅由 mcportal.mcp 需要,并作为可选的 [mcp] 额外依赖提供——不安装它,import mcportal 和整个测试套件仍可正常运行;调用 MCP 层时则会抛出一条韩语 ImportError,说明如何安装。[mcp] 额外依赖还声明了 anyio,因为同步转异步桥接会直接导入 anyio.to_thread;虽然 httpx 会间接引入 anyio,但直接导入的依赖应直接声明,以便固定版本和锁文件对其进行约束。

import mcportal 不会导入 mcportal.mcp——这正是未安装 fastmcp 时仍能正常导入的原因。MCP 符号会通过模块 __getattr__ (PEP 562)在首次访问属性时惰性解析,因此 from mcportal.mcp import build_server 和 mcportal.build_server 指向同一个对象。两种写法都可以。

单密钥原则(不轮换多个密钥)

MCPortal 的 data.go.kr 配置文件不支持轮换多个密钥。 data.go.kr 会为每个开发账户签发一个密钥,并据此统计每日调用限额;轮换多个密钥以绕过限额,可能违反服务的运营政策并招致账户处罚。MCPortal 尊重这一机制,接受单个密钥。当限额过低时,支持的做法是遵循 data.go.kr 自身的流程——注册用例并申请运营级别,而不是使用更多密钥。

CLI

CLI 仅使用标准库 argparse。本项目的硬性规定是不得新增运行时依赖,因此连终端表格也都是手工排版(韩文按双倍宽度计算,使用 ASCII 边框,在 Windows cp949 控制台上安全)。

mcportal quota status [--ledger PATH] [--budget N] [--day YYYY-MM-DD]
                      [--key-fp FP | --key-env VAR] [--json]
mcportal compile [PRESET_ID ...] [--presets-root PATH] [--check] [--json]
mcportal presets [--presets-root PATH] [--json] [--verbose]
mcportal sample PRESET_ID ... --key-env VAR [--budget N] [--count N]
                [--ledger PATH] [--presets-root PATH] [--json]
mcportal serve PRESET_ID [--replay | --key-env VAR] [--presets-root PATH]
                         [--name TEXT]
子命令功能
quota status显示每个密钥指纹今日(KST)的用量、预算、剩余量和状态。**账本以只读方式(mode=ro)打开,绝不会创建账本。**预算解析顺序为 --budget > CALL_BUDGET > 配置文件默认值,输出会说明采用了哪种方式
compile重新生成预设包。内容未更改的文件不会重写。--check 不写入任何内容,只进行字节比较
presets以表格形式列出预设包;--verbose 会展开整理说明
sample实时采样,补全仍标记为未解析的响应架构,并写入推断出的架构、响应正文和可重放的录制文件。这是唯一需要密钥的子命令,密钥只能从 --key-env VAR 指定的环境变量中读取,绝不会作为字面参数传入
serve通过 stdio 将一个预设包作为 MCP 服务启动。默认的 --replay 不需要密钥;--key-env VAR 会通过同一配额保护机制进行实时调用。需要 [mcp] extra。文档基于仓库检出版本编写:--replay 会读取 presets/<id>/cassettes/<id>.json,而 wheel 不包含该文件(注 1),因此从 PyPI 安装时,请传入 --presets-root <checkout>/presets 或设置 MCPORTAL_PRESETS=<checkout>/presets。四个预设包中有三个带有录制文件——仓库中也没有 15081808 的录制文件,因此它只能进行实时调用。stdout 仅用于协议传输;所有便于人阅读的内容(包括横幅)都会发送到 stderr

退出码:0 成功(包括“无账本”“无预设包”和“没有内容更改”——空状态不代表失败)/ 1 执行失败 / 2 用法错误 / 3 compile --check 检测到差异 / 130 用户中断。

--json 只在 stdout 上输出 JSON(不混入任何人类可读文本,可安全用于管道),并将所有错误发送到 stderr。任何情况下都不会打印原始服务密钥——即使使用 --key-env,也只会读取环境变量并在本地计算指纹。账本不会存储原始密钥,因此 CLI 能显示的只有指纹。

预设 — 三项服务,四个数据集

presets/ 中保存的是基于 data.go.kr 上真实发布的规范构建的捆绑包。它们旨在通过数据而非代码,展示如何修复朴素规范转换的失败模式:生成的每个工具最终都被描述成“列表查询”。

ID服务领域来源类型操作数已发布的数据许可
15000115法制处 — 国家法律信息共享服务法律rest_doc_manual8KOGL Type 1(署名)
15081808国税厅 — 营业执照有效性和状态查询营业执照odcloud_swagger2未注明限制
15101612关税厅 — 按国家统计的贸易数据海关gw_swagger1未注明限制
15102108关税厅 — 进出口汇总海关gw_swagger1未注明限制

许可措辞以各捆绑包记录的获取日期当天 data.go.kr 上显示的内容为准。按领域统计有三项服务,而海关有两个数据集,因此每份文档都写作**“三项服务(四个数据集)”**。

十二个响应架构中有十个尚未确定;2026-08-09 的实时采样确定了全部十个(15000115 八个、15101612 一个、15102108 一个——每项操作调用一次,共调用十次)。推断出的架构保存在各捆绑包的 sampled_schemas.json 中,响应正文保存在 samples/ 中,请求/响应对保存在 cassettes/ 中,因此无需密钥即可离线重放结果。已采样的捆绑包会在 info.x-mcportal 中报告 generation_mode: "sampled"。

其余两项操作(15081808)从未处于未确定状态——该来源声明了响应架构。我们特意未对该捆绑包进行采样,因为其请求正文包含营业执照号码;因此,它的架构已声明,但未经测量。MCPortal 不会隐藏这两种状态:实时计数会写入生成文档中的 info.x-mcportal.schema_inference.unresolved,而各捆绑包仍有哪些未知内容,则列在各自的 presets/<id>/README.md 中。将未经验证的内容写成已验证,与本项目的规则相悖。

一个捆绑包由四个已提交文件组成,有采样证据时还会附带采样证据:

presets/<id>/
├─ source.json           <- the spec document plus source URL, acquisition date, sha256
├─ curation.json         <- human-checked descriptions, examples, hints
├─ openapi.json          <- the merge of both layers (the committed artifact)
├─ README.md             <- provenance and open questions for this dataset
├─ sampled_schemas.json  <- schemas inferred from live samples (sampled bundles only)
├─ samples/              <- scrubbed response bodies from those calls
└─ cassettes/            <- request/response pairs for offline replay

顶部的四个文件会随 wheel 一同发布;从 0.2.2 起,存在时 sampled_schemas.json 也会一同发布——四个捆绑包中有三个包含该文件——因为没有它,compile --check 就无法重新合成 openapi.json(注 1)。记录的流量数据 samples/ 和 cassettes/ 仅保存在仓库中。

  • 底层不包含任何领域知识。 mcportal.compiler.curation 是一个通用引擎,用于读取、验证和合并整理数据;代码中不会出现任何机构或数据集名称,并且有测试通过扫描源字符串来确保这一点。
  • **整理不会更改规范中的事实。**它会添加描述、示例、标签和提示。参数类型、位置和必填状态,以及操作路径和方法,仍以来源规范的声明为准。只有两种渠道可以更正事实,而且都要求提供书面 reason:将响应架构降级为未确定,以及移除参数。
  • 对于相同输入,openapi.json 会逐字节一致地重新生成。使用 mcportal compile --check 作为 CI 门禁(出现偏差时退出代码为 3)。有一个例外:info.x-mcportal.tool_version 会记录软件包版本,因此升级版本会更改全部四个产物,而在此情况下重新生成是惯例。

约定和待处理事项由 presets/README.md 管理;规范元数据的来源和使用条款由 presets/NOTICE-DATA.md 管理。

架构

编译时分为两层,运行时则是一条带防护的调用链。

COMPILE TIME  (offline: no key, no network)

  spec documents                      +--------------------------------+
   - odcloud OAS (JSON)               | lower layer: the compiler      |
   - gateway Swagger 2.0 / 3.x  --->  | zero domain knowledge          |
   - hand-mapped usage guide          | sources -> SourceSpec -> IR    |
                                      +---------------+----------------+
                                                      |
  curation.json                                       |
  (human-checked descriptions,  --------------->    merge
   examples, hints)                    upper layer: data, not code
                                                      |
                                                      v
                                         openapi.json (committed;
                                         byte-identical on rebuild)

RUN TIME  (one MCP tool call)

  MCP client
      |  tool call
      v
  FastMCP server            <- built by FastMCP.from_openapi() from openapi.json
      |
      v
  sync/async bridge  ->  MCPortalTransport
                            |-- quota guard      token bucket + SQLite ledger
                            |                    + CALL_BUDGET hard cap + backoff
                            |-- key injection    the key never enters the spec
                            |-- TTL cache
                            |-- record / replay  cassettes, scrubbed on write
                            |-- normalization    XML -> JSON, EUC-KR, error codes
                            v
                     data.go.kr      (or the cassette, in replay mode)

这种架构有两个值得明确说明的结果。

  • 工具定义不是手动生成的。 FastMCP.from_openapi() 负责将规范转换为工具;MCPortal 提供它之前的阶段(规范规范化)和之后的阶段(配额与清理)。不同版本系列之间的差异通过运行时签名内省来处理:如果类公开了 FastMCP.from_openapi,就使用它;否则回退到构建 FastMCP(providers=[OpenAPIProvider(...)])。这是能力检查,而不是版本检查——from_openapi 并非仅限于 2.x 的入口,它在 3.x 系列中也存在。依赖固定为 fastmcp==2.14.7,因为在录制回放测试中实际验证过这一组合;已知的硬性边界是 4.0,它将 HTTP 堆栈迁移到 httpx2>=2.5,因此会破坏本项目所依赖的 httpx.AsyncBaseTransport 桥接(4.0 还会再次拆分发行包,改为 fastmcp-slim)。固定该版本的理由记录在 pyproject.toml 中,紧挨着版本声明。
  • 服务密钥无处泄漏。 编译器不会生成 security 或 securitySchemes,并会从源文件中移除密钥参数,因此密钥永远不会成为 MCP 工具参数,不会出现在规范文件中,也不会出现在提示词日志中。仅保留密钥由传输层注入这一事实,即 info.x-mcportal.key_injection: "transport"。

每条默认路径都会接入防护。预算的解析顺序为 create_client(budget=...) > CALL_BUDGET 环境变量 > 配置文件默认值;即使省略该参数,也仍会接入防护——既然 README 声明硬性上限是信任的依据,就不能让防护悄然消失。在 before_call 时进行中的请求会预留预算,并在 after_call 时释放,因此即使 MCP 服务同时发起多个工具调用,上限也依然有效。

基准测试

测量计划已预先注册。 benchmarks/PROTOCOL.md 在测试框架创建之前就确定了测试项、重复次数、统计定义和局限性,测试框架不会测量该文档中未列出的内容。结果文件中嵌入了协议指纹,因此事后仍可核查某个数字对应的协议版本。

python benchmarks/harness.py --label <label>

测量 5 个无需密钥的测试项(回放往返、脱敏、编译与确定性、配额保护开销、FastMCP 构建)。网络调用数为 0;输入要么完全合成,要么使用已提交的预设。不会剔除异常值——原始样本随结果文件一同提供,任何人都可以重新计算。

核心数据:配额保护开销的中位数为每次调用 +0.90 ms (+901,050 ns;保护后的中位数为 1.04 ms,裸调用的中位数为 0.14 ms;预热 20 轮后测量 N = 200;测量日期为 2026-08-09,环境为 Windows 10、CPython 3.11.9、httpx 0.28.1、SQLite 3.45.1)。

这一核心数据取决于环境,环境也是该结论的一部分。 2026-08-15 在一台全新配置的机器上重新运行同一测试框架后,保护后的中位数为 738.9 us,裸调用的中位数为 91.0 us,增量为 +0.65 ms。这两个数字都没有错:绝对开销取决于主机的 SQLite 写入延迟,因此第三方复现的数字会是他们自己的结果。引用核心数据时,请附上测量环境,或者自行重新测量。

请将该数据理解为绝对增量,并同时考虑以下两点。该数字包含 SQLite 日志写入(WAL 日志模式),因为这项写入属于实际开销。作为减数的基线是通过内存中的 httpx.MockTransport 运行的裸 httpx.Client——没有套接字,也没有 I/O——因此用百分比表示同一测量结果时,数值天然会很大,单独看没有意义。与真实的 data.go.kr 往返相比,结果会有所不同;MCPortal 在此不对这种对比作出结论,因为尚未进行测量。

这些数字衡量的是 MCPortal 自身增加的这一层的开销,并非与其他库的对比。 用于判断两层设计是否值得采用的测试项(K2——朴素生成与精心整理的工具调用成功率)仅在协议中作了定义,尚未运行。提前写下定义是有意为之:这样可以避免在结果出来后挑选有利标准。0.2.0 中使用的有效密钥仅用于响应模式采样;依赖密钥的基准测试项 K1–K3 不在此版本的范围内。

机器可读的预设根目录

mcportal presets --json 会在 root 旁报告 root_source 键,标明所采用的预设根目录来源:argument(显式指定 --presets-root)、env:MCPORTAL_PRESETS、discovered(通过默认搜索找到)或 none(完全没有根目录)——这样脚本就能区分有意指定的根目录和意外采用的根目录。

许可

Apache-2.0。请参阅 LICENSE 和 NOTICE。

数据衍生文件(测试固件、响应样本、编译器产物)的来源和使用条款单独记录在 NOTICE-DATA.md 中。

  • 没有提交任何通过调用 API 获得的响应载荷。 测试固件、录制文件和演示产物均为合成内容,且无需密钥的复现流程完全基于这些合成固件。
  • 公共 API 的规范元数据(Swagger 文档、请求/响应表)存放在 presets/ 下,并记录其来源 URL 和获取日期。获取过程完全未经身份验证:未使用任何服务密钥,也未调用任何网关数据接口。规范文档包含门户为文档目的编写的示例值,这些值均为占位符;各文件的来源、条款和示例值清单由 presets/NOTICE-DATA.md 规定。
  • 不包含任何个人身份信息(真实姓名、真实企业注册号、个人电话号码、个人电子邮件地址)。此声明适用于数据包文件,即 source.json、curation.json、openapi.json 和每个 README.md。presets/_raw/ 下的门户页面快照仍保留了运营机构的公共帮助台电子邮件地址([email protected])、主电话号码(1566-0025)以及各数据集管理部门的代表号码,因为这些信息出现在原始页面中;它们是机构联系信息,而非个人信息。相关证据和完整清单由 presets/NOTICE-DATA.md §2-1 规定。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。