Skip to main content
Glama
PNX89

QUENCHZ

by PNX89

QUENCHZ

一个 stdio MCP 服务器因为拥有一个共享其上下文的调用方,而免费获得四样东西。一旦传输变为远程,每一项都会成为一个决定,而每一个决定都是在这里公开做出,并从 Python 代码树之外得到证明。

CI Python TypeScript client License: MIT Data: ECB

向并非你编写的调用方提供 MCP 工具。这四个方面是 受众触达预算覆盖,每一项都可以通过一个评审者能眼睁睁看着失败的测试来证伪。

{
  "observations": [
    ["2026-03-30", 1.1484],
    ["2026-03-31", 1.1498],
    ["2026-04-01", 1.1605],
    ["2026-04-02", 1.1525],
    ["2026-04-07", 1.1557],
    ["2026-04-08", 1.1706],
    ["2026-04-09", 1.1685],
    ["2026-04-10", 1.1711]
  ],
  "coverage": {
    "requested_from": "2026-03-30",
    "requested_to": "2026-04-10",
    "requested_calendar_days": 12,
    "expected_observations": 8,
    "delivered_observations": 8,
    "delivered_from": "2026-03-30",
    "delivered_to": "2026-04-10",
    "absent": {
      "target_closed": 4,
      "not_yet_published": 0,
      "no_such_observation": 0,
      "before_the_series": 0
    },
    "window_still_open": false,
    "body_completed_at": "2026-08-26T12:00:00Z",
    "source": "ECB statistics."
  },
  "source": "ECB statistics."
}

要求了十二个日历日,返回了八个,而且 响应是完整的。耶稣受难日、复活节星期一和周末是休市而非缺口,而厂商的 payload 对其中任何一个都只字未提。要区分这两者,需要发布日历和墙钟时间;这正是 coverage 块的作用:[src/quenchz/coverage.py]。

它并不对所有情况建模。它重构了三种已命名的缺失原因,以及第四种针对序列存在之前的日期;而第五种没人想到过的原因会被计为真正的缺口。证书是一个必填字段且没有默认值,因此返回观测数据的工具没有证书就无法作答。

来源:ECB 统计数据。

要求了十二天,交付了八天

上面的证书不是写在数据旁边的摘要。它是返回值上的一个必填字段,是根据发布日历重构的,而不是从响应中读出的,因为响应中并不包含它。

字段

它回答什么

requested_calendar_days

调用方要求了什么

expected_observations

日历表明曾经应有多少

delivered_observations

实际到达了什么

absent.target_closed

市场当时休市

absent.not_yet_published

它今天应发布,但尚未发布

absent.no_such_observation

它本应发布,它迟到了,并且应该有人知道

absent.before_the_series

从来没有应发布之说,因为序列尚未开始

最后一行是在一次评审要求查看 1990 年的时间窗口后加入的。1990 年欧元还不存在,而证书给出的答案是 expected_observations: 261,并把全部 261 个都计为真正的缺口。这是可给出的最糟糕答案:在并不欠付任何一天的日子里,自信地报告 261 个缺失汇率。

Related MCP server: OPNsense MCP

一个有效却触及不到任何东西的令牌

一个由受信任的签发者签发、签名正确、尚未过期、携带正确 scope、但指定给另一个资源的令牌,在这里什么也触及不到。

这个检查必须手工编写。SDK 在 AccessToken 上带有一个 RFC 8707 的 resource 字段,却从不拿它和任何东西比较:BearerAuthBackend 会检查 Bearer 前缀、真值性和过期时间,然后让 resource 原封不动地通过。SDK 确实告诉签发者要对其签发的令牌做受众限制,但关于资源服务器是否核验这一点,它完全没提。有两个测试断言 SDK 仍然如此表现,所以如果某个版本开始强制核验,这个声称会响亮地失效,而不是悄无声息地变成假话。

受众是列表的令牌同样会被拒绝,而 PyJWT 会接受它。 RFC 7519 将受众视为一种成员资格测试,因此一个同时指定给此资源另一个资源的令牌能通过该库。但它在这里通不过:一个在其他地方也有效的令牌,就是其他地方能拿到这扇门前出示的令牌,这正是 RFC 8707 所要避免的情况。

调用方也无法得知它不能使用什么。一个在其被授予的 scope 之外的工具不会出现在它的清单中;无论如何去请求它,返回的字节与请求一个从不存在的名称完全相同。这确实要付出代价:真正打错 scope 的人得到的提示信息不如本可以得到的那么有帮助。但这值得,因为 SDK 自身的 scope 拒绝会回复 Required scope: <name>,这确认了工具存在,并点明了本来可以生效的授权;而针对这一点进行一百次猜测,就等于让服务器为一个从未被允许看到攻击面的调用方绘制出攻击面地图。

启动服务器

uv sync --dev
uv run pytest

该服务器带有进程内签发者,会把自身签发的令牌写入一个文件:

uv run python -m quenchz.interop_server --port 8931 --tokens-file tokens.json

签名密钥在内存中生成,从不落盘。每个主机名都使用 RFC 2606 中保留的 .invalid 顶级域,永远无法解析。

两个调用方与一个两者都看不到的预算

厂商没有在任何页面记录速率限制,没有在任何响应中发送速率限制头,也没有在状态码中列出 429。因此,在这里采用被动式限流不是不完美,而是不可能:两种常规信号都不存在,而且你无法对你观察不到的东西做退避。所以预算是事先选定的,并且是自我施加的。它并不与厂商强制执行的任何限制相匹配,本文也不声称它匹配。

朴素设计保留在源码中,因此这种差异是一种度量,而不是一种断言。两种设计采用相同的到达模式:

设计

贪婪方被准入

安静方随后被准入

一个共享桶

60

0 / 60

各自一个预留,外加一个共享备用

45

15 / 60,恰好是它的预留

被拒绝的调用也要计费。如果拒绝是免费的,那么为了让它们完全一致所花的功夫就白费了:调用方根本不需要去解读它们,只需要问一百个名字,看哪一些改变了自己被准入的速率。

从 Python 之外证明它

[clients/typescript/src/prove.ts] 使用官方的 MCP TypeScript SDK 驱动正在运行的服务器,运行在它自己的 CI 任务中。它与其测试的对象不共享代码,这一点很重要:Python 测试套件和 Python 服务器可能在某个对协议而言不成立的事情上彼此达成一致。

共十六项检查。针对此资源的令牌以 200 被准入;针对另一个资源、两个资源或没有资源的令牌以 401 和 Bearer 质询被拒绝。未获授权的工具和不存在的工具以完全相同的字节拒绝。突发调用方在算术所表明的位置停下,而安静调用方仍然恰好得到它的预留量。

这个证明本身有一个值得记录的缺陷。它的第一个版本只问 connect() 是否抛错,并把任何抛错都当作拒绝;因此,当一个故意弄坏的构建让验证器抛错、服务器返回 500 时,证明却报告成功。一个对所有请求都回 500 的服务器,本可以满足页面上每一项受众检查。现在它改为读取状态码。

厂商写下的,而 payload 没有的

这里涉及三个时间,而客户端只能看到其中两个。

时间

它是什么

它存在于哪里

约 14:10 CET

各央行之间的协调

厂商的文字说明

14:15 CET

汇率所指向的时刻

在 payload 中,即 TITLE_COMPL

约 16:00 CET

汇率实际发布的时刻

仅厂商的文字说明

客户端如果读取 payload 自身的元数据,并据此认为汇率在 14:15 就可获取,那就错了将近两个小时,而且每天下午都会把尚未发布的日子当成真正的缺口。payload 不仅仅是忽略了这一约束。它还携带了一个看似权威、却不该使用的时间。

缺失的编码方式也变了,这是通过编写一个统计行数的客户端、并发现其数字比统计数值的客户端大 62 而发现的。整个序列共有 7,140 行,包含 7,078 个数值62 个占位符:直到 2012 年 5 月,一个收盘日会以一行空值和 OBS_STATUSH 的形式出现;之后则干脆没有那一行。同样的日历,两种编码,只有统计数值在两个时期都是正确的。

四个决定,以及各自的代价

受众是门禁,而不是签名。 代价:一个在其他地方有效的令牌在这里被拒绝,因此持有宽泛 scope 令牌的调用方必须回到它的签发者那里换取一个更窄的。

Scope 在调度时被拒绝,而绝不由传输层拒绝。 代价:scope 打错的合法调用方得到的拒绝不会告诉它任何信息。

每个调用方都有一个别人无法动用的预留量。 代价:孤立的调用方从中得不到任何好处,而且当另一个调用方恰好空闲时,突发流量不再能使用整个预算。

证书是必填的,且没有默认值。 代价:每个返回观测数据的工具都必须计算一个证书,包括那些回答是“什么也不缺”的工具。

局限

  • 签发者是本地且进程内的。本文不对任何真实身份提供者作任何声称,这里的一切也从未接近过任何真实身份提供者。

  • 预算是自我施加的。它不对应厂商发布的任何限制,因为厂商没有发布任何限制。

  • 证书重构了四种已命名的原因。它没想到的第五种原因会被计为真正的缺口,这是安全的方向,但并不等于完备。

  • ECB 参考汇率是每日发布的参考值,不是可交易的价格,这里的一切也不会把它们当作可交易价格。这里没有订单簿、没有撮合引擎、也没有执行路径。

  • 这些录制数据(cassettes)是有日期的。它们只能证明厂商在录制当天返回了什么,不能证明厂商今天返回什么。

  • 这里的一切均未获得 ECB 的认可,也与 ECB 无任何关联。

开发

uv run pytest
uv run python scripts/readme_block.py

本页上的每一个数字和每一块代码都由 tests/test_readme.py 针对它所描述的对象进行检查。上面的响应体是生成的,不是抄录的。

许可证

MIT。参见 LICENSE

这些数据依据 ESCB 统计数据复用政策 复用;该政策允许免费使用,但有三个条件:注明来源、不得修改统计数据和元数据、且不得包含第三方数据。这三个条件在 [tests/test_licence.py] 中都是测试,而不是承诺。来源:ECB 统计数据。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enforces fine-grained, context-aware access control on MCP tool calls, with a tamper-evident, replayable audit log that records denials and verifies every decision.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables remote agents to securely read firewall and NAT rules, inspect routing tables and logs, and execute safety-gated mutation plans through a bearer-authenticated MCP endpoint.
    -
  • A
    license
    A
    quality
    C
    maintenance
    Enables policy-governed MCP interactions with deterministic authorization, tenant isolation, minimized PII exposure, and human approval gates for sensitive mutations, while producing structured audit events.
    3
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables MCP clients to govern downstream tool servers by enforcing default-deny authority and attestation on every tool call, with live monitoring, approval, and mid-session revocation.
    3
    Apache 2.0