12. Sources、MCP、API 与凭据
Source 是 Craft Agents 最具产品辨识度的抽象:用户不是在全局安装一堆工具,而是在 workspace 定义外部能力,再按 session 启用。MCP、REST API 与本地资源最终都变成统一工具名和 guide-driven 使用体验。
12.1 Source 领域类型
Section titled “12.1 Source 领域类型”sources/types.ts 的主 discriminant:
type SourceType = 'mcp' | 'api' | 'local'- transport:stdio/http/sse;
- command/args/env 或 URL/headers;
- auth:none、bearer、OAuth 等;
- 可选 OAuth client metadata。
- base URL;
- endpoint/tool 描述;
- auth:none/bearer/header/query/basic/oauth;
- provider-specific Google/Slack/Microsoft;
- generic OAuth;
- 非 OAuth renew endpoint;
- custom header names/default headers。
描述本地目录/资源类型,受 workspace local MCP/文件权限约束。
12.2 生命周期
Section titled “12.2 生命周期”stateDiagram-v2 [*] --> Configured: source config exists Configured --> NeedsAuth: auth required, no valid credential NeedsAuth --> Authenticated: OAuth / credential prompt Configured --> Enabled: no auth or valid credential Authenticated --> Enabled: session selects source Enabled --> Connected: builder + pool sync Connected --> Enabled: connection failure / token expiry Connected --> Inactive: session deselects Inactive --> Connected: runtime activationisAuthenticated 是方便 UI/配置的状态,不应成为唯一真相;真正执行前仍要从 credential manager 获取有效 token。
12.3 从目录到工具
Section titled “12.3 从目录到工具”完整链:
sources/<slug>/config.json→ storage + schema validation→ LoadedSource→ SourceCredentialManager 获取/刷新 secret→ SourceServerBuilder MCP → SdkMcpServerConfig API → in-process McpServer→ McpClientPool.sync()→ listTools + cache→ mcp__<slug>__<tool> ProxyToolDef→ Claude/Pi register tools→ tool call 回 McpClientPool.callTool()每一层都把复杂度往下收敛:backend 不知道 OAuth,模型不关心 transport,UI 不持有 MCP client。
12.4 SourceServerBuilder
Section titled “12.4 SourceServerBuilder”MCP stdio
Section titled “MCP stdio”要求 command,返回:
{ type: 'stdio', command, args, env }是否允许本地 stdio 还由 workspace localMcpEnabled 控制。远程 server 的“local”指服务器本地,不是用户 Electron 本机。
MCP HTTP/SSE
Section titled “MCP HTTP/SSE”URL 规范化;header 以优先级合并:
静态非 secret headers < credential-store multi headers < OAuth/Bearer Authorization若 config 声称已认证但 token 丢失,builder 返回 null/error,不能创建一个必然 401 的“connected” source。
API source 被包装成 in-process MCP server。不同 auth:
- Google/Slack/generic OAuth:每请求调用 token getter,支持刷新;
none:空 credential;- renew endpoint:用动态 token getter;
- api key/header/query/basic:优先用每请求 credential getter;
- 只有测试/legacy caller 才用构建时静态 credential。
“每请求 getter”修复一个典型热更新 bug:用户在 credential prompt 粘贴新 JWT 后,旧 tool closure 不能继续永久捕获旧 token。
12.5 API 如何变成 MCP
Section titled “12.5 API 如何变成 MCP”api-tools.ts 根据 source config 创建 tools:
- JSON schema 描述 path/query/body;
- 执行时拼 URL/header/auth;
- 校验 endpoint/URL;
- 解析 JSON/text/binary;
- 大响应落文件并可摘要;
- mutation metadata进入权限管道。
模型看到的仍是 mcp__slug__toolName,Claude/Pi 不需要专门的 REST 工具协议。
这是一种很实用的统一:MCP 是工具承载格式,不要求外部服务本身实现 MCP。
12.6 中央 McpClientPool
Section titled “12.6 中央 McpClientPool”McpClientPool 在主进程拥有所有 source connection。
内部表:
clients: slug → MCP/API PoolClientactiveConfigs: slug → connection configtoolCache: slug → Tool[]proxyTools: proxyName → {slug, originalName}- Claude/Pi 共用一条连接/错误处理;
- secret 不写 provider 子进程配置;
- source token 更新只重连 pool;
- tool result guard 一处实现;
- 可复用连接与统一 debug;
- runtime source switching 不依赖 provider 原生 MCP lifecycle。
12.7 sync() 是 reconcile,不是全量重启
Section titled “12.7 sync() 是 reconcile,不是全量重启”- 若 workspace 禁 local MCP,过滤 stdio;
- 计算 desired/current slugs;
- 断开已移除 source;
- 连接新 MCP;
- URL 或 Authorization 改变时只重连该 source;
- 连接新 in-process API server;
- 收集失败 slugs,不因一个失败取消全部;
onToolsChanged()通知 backend 更新定义。
配置 change detection 当前重点比较 transport、URL 与 Authorization。若未来影响连接的其他 header/env 改变,也要纳入签名,否则 pool 可能继续用旧配置。
12.8 Proxy tool 命名
Section titled “12.8 Proxy tool 命名”工具统一为:
mcp__<sourceSlug>__<originalToolName>getProxyToolDefs() 从缓存 tool schema 派生,并移除顶层 $schema,因为 Pi/AJV 未注册某些 meta-schema URI 会拒绝。
命名同时提供:
- 全局无冲突 namespace;
- PreToolUse 可提取 source slug;
- result/error 能归因到 source;
- UI 能按 source icon/名称展示。
12.9 Tool execution 与结果保护
Section titled “12.9 Tool execution 与结果保护”proxyName → slug/originalName→ 找 PoolClient→ callTool(originalName,args)→ normalize content blocks→ text 拼接→ image/audio base64 解码并保存 downloads→ guardLargeResult(落文件/摘要)→ {content,isError,sourceSlug?}非标准 MCP server 可能把 text 返回 object,代码会 JSON stringify,而不是崩溃。Binary 用 magic bytes 检测扩展名,不完全信任 MIME/文件名。
大响应保护很关键:source API 一次返回几 MB JSON,如果原样回模型,不仅超 context,还会把 session JSONL 和 UI 卡死。
12.10 Source 激活与热更新
Section titled “12.10 Source 激活与热更新”模型调用 inactive source tool 时,PreToolUse 返回 activation needed。成功路径:
SessionManager 激活 source→ load credential / build server→ pool.sync→ backend re-register proxy tools→ source_activated AgentEvent→ 对原 user message 做去重 auto-retryAuto-retry 需要 key,防 source activation/event 重复导致无限重发。工具变化只在安全 turn boundary 对 Pi 重建 session;Claude 可根据 SDK server/tool机制更新。
12.11 Credential identity
Section titled “12.11 Credential identity”中央 CredentialManager 用结构化 CredentialId 区分:
- LLM connection;
- workspace/source;
- credential name/type;
- OAuth/API key/IAM/service account 等形态。
Source config 不保存 secret,只保存 client id、auth scheme、header names、endpoint 等非秘密元数据。多 header credential 是 map,而不是把多个 key 拼成一个字符串。
12.12 加密存储
Section titled “12.12 加密存储”SecureStorageBackend 的格式:
64-byte header magic CRAFT01\0 flags 32-byte PBKDF2 salt reservedpayload 12-byte random IV 16-byte GCM auth tag ciphertext(JSON)Key 由 OS 稳定机器 ID 派生;旧 hostname key 可自动迁移。每次写新 IV,权限 0600。
Environment backend 代码存在但 isAvailable() 返回 false,当前设计要求用户显式输入 secret;provider connection 仍可能按 authType 直接从运行环境解析特定变量,这与通用 credential backend 是两条路径,需区分。
12.13 过期判断与刷新窗口
Section titled “12.13 过期判断与刷新窗口”CredentialManager 的 isExpired():
- 有
expiresAt:提前 5 分钟视为过期; - 无 expiry 但有 refresh token:保守视为过期,触发刷新;
- 无 refresh token 的 API key:视为长期有效。
源码:CredentialManager.isExpired。
SourceCredentialManager 再按 provider/renew endpoint 执行刷新。并发 refresh 应合并/串行,避免 rotation token 被两次使用导致其中一条作废。
12.14 OAuth 变体
Section titled “12.14 OAuth 变体”支持:
- Claude OAuth + PKCE;
- Google;
- Slack user OAuth/rotation;
- Microsoft;
- 通用 API OAuth;
- MCP OAuth progressive discovery;
- Cloud relay/deeplink,用于要求 HTTPS 或跨设备回调的场景。
Provider-specific code处理 scope、token response 和 refresh 差异;共同 flow 通过 OAuthFlowStore 保存短期 state/TTL 与 session/workspace context。
12.15 MCP OAuth discovery
Section titled “12.15 MCP OAuth discovery”auth/oauth.ts 采用渐进发现:
- RFC 9728:访问 protected resource,解析 401
WWW-Authenticate的resource_metadata; - 读取 protected resource metadata 中 authorization server;
- 获取其 authorization server metadata;
- fallback 到 RFC 8414 well-known 候选;
- 可选 dynamic client registration;
- PKCE + state;
- code exchange/store。
这比假设 /.well-known/oauth-authorization-server 固定在 MCP host 根路径更兼容真实服务。
12.16 SSRF 防护
Section titled “12.16 SSRF 防护”OAuth discovery 会 fetch source 提供或远端 header 指向的 URL,是典型 SSRF 面。isUrlSafeToFetch() 拒绝:
- 非 HTTPS;
- localhost;
- 私网 IPv4;
- link-local /
169.254/16(云 metadata); - 其他明显内部地址。
源码:auth/oauth.ts。
基础字符串/IP 检查仍可能受 DNS rebinding、IPv6 映射、redirect 影响。高安全部署应在每次 redirect/DNS resolution 后验证最终地址,并从网络层禁止 metadata/private egress。
12.17 OAuth 在远程模式的分工
Section titled “12.17 OAuth 在远程模式的分工”Headless server 拥有 credential 与 source,但用户浏览器在 client:
server prepare flow + state→ RPC auth_request(authUrl)→ Electron/WebUI 打开本地浏览器→ callback/deeplink/cookie 返回 code→ server exchange + encrypted store→ source pool refresh→ session retryPreload 注释强调 OAuth 浏览器交互要在客户端编排,因为 headless host 不能假设有 GUI。
12.18 Guide 与工具描述
Section titled “12.18 Guide 与工具描述”Source 的 guide.md 承担长文档;tool description 只保留操作摘要与 schema。PrerequisiteManager 确保第一次使用前读 guide。
这是 Source 自助配置的关键:Agent 可以创建 config + guide,source_test 做 schema/连接/auth/完整性检查,并默认 enable + 当前 session activate,无需应用发布新代码。
12.19 失败与降级
Section titled “12.19 失败与降级”| 故障 | 行为 |
|---|---|
| 一个 source 连接失败 | 记录 slug,其他 source 继续 |
| token 缺失/过期 | needs auth/refresh,不注册坏 server |
| tool 不存在 | proxy 返回结构化 error |
| MCP client 断开 | sourceSlug 归因,允许重连 |
| 二进制返回 | 落 session downloads,返回路径 |
| 大文本 | 落 long response + 可选 summary |
| 新 credential 输入 | getter 下一请求读取,无需 restart |
| tool schema 变化 | cache 更新,backend安全边界重注册 |
| local MCP 被禁 | sync 时过滤 stdio |
12.20 设计评价
Section titled “12.20 设计评价”优点:
- Source 是文件化、可由 Agent创建的扩展点;
- MCP 与 REST 统一为 tool;
- pool 集中 secret、连接、结果保护;
- 每 session enable,减少不必要工具/context;
- OAuth/credential 热刷新贯穿执行链。
风险:
shared中 source/auth 代码量很大,provider 分支持续增长;- MCP mutation classification 依赖外部质量;
- connection change signature 需覆盖所有 header/env;
- generic OAuth/URL 是高风险输入面;
- 多 session pool 共享与 sessionPath-specific result storage 需明确实例作用域。
12.21 本章小结
Section titled “12.21 本章小结”Source 子系统把“连接一个外部服务”拆成可验证的文件配置、独立秘密、动态 server builder、中央 connection pool 和 backend proxy tools。真正的价值是热更新链闭环:凭据或 source 改变后,不必重启整个产品就能在当前 session 使用。
下一章回到 session 数据生命周期,详细看 JSONL 写入队列、严格 branch、bundle transfer 与 public share。