@cyberred/mcp-local
v0.1.1
Published
Local stdio MCP server for the CyberRedd API (runs on the user's machine; API client only)
Maintainers
Readme
@cyberred/mcp-local
本地 stdio MCP server:把 CyberRedd 冻结的 operation 面暴露成 MCP 工具,转发到远端 REST API。
跑在用户设备上(架构 §2 的不可信区,D27)。它是一个纯 API 客户端 —— PDP 判定、额度预留、permit receipt、obligations 全部在服务端,本包一条都不复刻。
用法
先授权一次(Device Code,D114④ / D116②;token 只存 OS keychain,不写进任何配置文件):
cyberred login --for mcp --issuer <授权服务器地址> --base-url https://api.cyberredd.com
# 终端打印验证码与 Console 的 /device 链接;在浏览器里批准并勾选这次授予的权限然后在宿主客户端里只配 base URL:
// 宿主客户端的 MCP 配置
{
"command": "cyberred-mcp",
"env": {
"FORUMLAYER_API_BASE_URL": "https://api.cyberredd.com"
}
}- 启动时从 OS keychain(macOS 钥匙串 / Linux Secret Service,经
@cyberred/credential-broker)取--for mcp那一份票,到期前自动用 refresh 轮换。取不到、轮换被拒、keychain 不可用 ⇒ 启动失败并提示 「请运行 cyberred login --for mcp」。没有任何回落路径。 - 🔴
FORUMLAYER_API_TOKEN(旧的静态 key)已删除:环境里还带着它时进程拒绝启动 (STATIC_KEY_REJECTED),而不是静默忽略 —— 否则那把 key 会继续躺在可提交的配置里。 - CLI 的人工凭据(
cyberred login,不带--for mcp)与 MCP 凭据是 keychain 里两个条目,本包只读后者; MCP 条目若用的是 CLI 的 client(cyberred-cli)同样拒绝(ARCHITECTURE §10.3.2「不复用 CLI 的人工凭据」)。 - Windows 今天没有 keychain 后端(
KEYCHAIN_UNAVAILABLE),不会退回写文件。 FORUMLAYER_API_BASE_URL必填,没有"未配置即用默认端点"的路径。
本包已登记进 scripts/publishable-packages.mjs(连同它依赖的 @cyberred/mcp-protocol),pnpm check:packages 会核对 tarball 内容;但今天还没有发到 npm 上 —— 发布是一次 owner 决策(docs/npm-packages.md),仓内只做到 dry-run。
三件先读的事
| 事 | 在哪 |
|---|---|
| 工具面是算出来的,不同步会让进程起不来 | @cyberred/sdk/mcp 的 tool-surface.ts |
| stdout 被整个拿走,任何日志都落 stderr | src/guard.ts |
| 请求构造(path 模板、编码、query、幂等键要求)全在 SDK 的 planOperationRequest();字段路由在 @cyberred/sdk/mcp 的 routeToolArguments()(Remote MCP 与 CLI 共用同一份),那一半仍是 contracts 生成器规则的镜像(已知缺口) | @cyberred/sdk/mcp 的 binding.ts |
两条要知道的语义
凡返回 CallToolResult,必带符合 outputSchema 的 structuredContent。 本地失败(未知工具、绑定失败、连不上、响应不合契约)走 JSON-RPC error,不走 isError 结果 —— 因为那时没有 canonical 信封可给,而现编一个等于伪造服务端判定。服务端返回的 error 信封则照常是带 structuredContent 的 isError: true 结果。
wire error 原样转达,含 error.details。 这是显式取舍:我们发布的 outputSchema 就是完整 canonical union,在客户端裁剪会让"广告的"与"发出的"对不上,也破三通路 parity。代价是 details 里的 field / limit / tier / policy_reason_codes 会到达宿主与模型。
今天连不上真服务
全仓还没有可跑的 API 服务(apps/api 里第一个在建,只有 pack.get)。七个工具没有一个连得上真服务端,端到端只对着 test/support/stub-api.ts 里的受控桩跑 —— 桩按冻结 wire 契约回话、并用 contracts 的校验器双向验证,但它没有 PDP、没有额度、没有 permit receipt、没有限流。
(曾经记在这里的已知阻断——规范社区 ID reddit:r/example 含 /、编码后被 SDK 的 buildRequest 以 PATH_UNSAFE 拒绝——已经解除:请求构造收敛到 SDK 的 planOperationRequest() 之后,段内 %2F 是 SDK 自己对单个参数值编码产生的,那条只对无出处裸字符串成立的规则不再适用。test/binding.test.ts 与 test/e2e-protocol.test.ts 里原先钉住阻断的用例已转为正向用例。
⚠️ 这不等于线上可用。 SDK 只保证它发出的原始 request-target 保持模板结构,保证不了网关 / CDN / 路由在解码或归一化之后还这么看;本仓在建的 apps/api 今天对 pack id 里的 /(含 %2F)仍然是拒绝的。含 / 的 ID 能不能真正端到端服务,是一个尚未裁定的网关/路由决策。
