feat(tools)!: carry MCP credentials as profile config references - #355
Conversation
`@tangle-network/agent-interface` 0.38.0 replaced plain strings on an
`AgentProfileMcpServer` with tagged `AgentProfileConfigValue`s. Verified
by running both validators against real payloads:
payload 0.36.0 0.40.0
headers Authorization string ACCEPTED REJECTED
headers Authorization tagged REJECTED ACCEPTED
headers Authorization public REJECTED REJECTED (credential-bearing
config names require a secret-ref)
agent-app emitted the old shape, so every consumer that bumped
agent-interface had to hand-write an adapter (gtm-agent wrote 89 lines)
and origin/main does not even COMPILE against 0.38.0+ — the
`as AgentProfileMcpServer` cast at src/sandbox/index.ts:464 becomes
TS2352 the moment the type is real.
`Authorization` now emits `defineAgentProfileSecretRef(tokenEnvKey,
'bearer')`; every other header emits `defineAgentProfilePublicConfig`.
A secret reference resolves ONLY from an environment variable on the box,
so the builders take the box-env variable NAME (`tokenEnvKey`) rather
than a token VALUE — agent-app cannot guess that name, and a reference to
a key nothing places fails at materialization instead of running
credential-less. `SandboxRuntimeConfig.env` stays `Record<string,string>`
on purpose: it is the private resolution SOURCE, the SDK create payload
types it that way, and `assertEnvWithinLimits` measures those bytes.
Every emitted entry is validated against the real
`agentProfileMcpServerSchema` instead of a re-implemented copy of its
rules, which also turns a pre-0.38.0 resolution into a loud named failure
rather than a silent pairing.
`unresolvableSurfaceCredential` is exported for the case the substrate
still cannot serve: a per-(user, resource) token cannot be placed on a
workspace-wide box env, so design-canvas / sequences channels that need
one name that blocker instead of emitting an unresolvable reference.
BREAKING CHANGE: `buildHttpMcpServer`, `buildAppToolMcpServer`,
`buildScopedMcpServerEntry`, `buildAppToolMcpServers`,
`buildDesignCanvasMcpServerEntry` and `buildSequencesMcpServerEntry` take
`tokenEnvKey` (a box-env variable name) instead of `token` (a value), and
`AppToolMcpServer.headers` is now
`Record<string, AgentProfileConfigValue>`. There is deliberately no
plain-string branch: accepting one would re-admit raw credentials into
profile material, which is what the 0.38.0 contract exists to prevent.
The peer range stays `>=0.36.0`: agent-runtime@0.109.2 peers
`>=0.36.0 <0.37.0` and agent-profile-materialize@0.9.3 peers `<0.38.0`,
so tightening it today would make the graph unsatisfiable for the ten
products that install them. The build-time schema check is the guard
until those republish.
CI heap 8192 -> 12288: `AgentProfileConfigValue` reaches five entry
points, so rollup-dts inlines agent-interface's schema graph into each.
Measured here: 8192 ERR_WORKER_OUT_OF_MEMORY, 10240 green at 9.4 GB peak
RSS / 38 s.
tangletools
left a comment
There was a problem hiding this comment.
✅ Auto-approved drewstone PR — 5e2108c7
This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.
tangletools · auto-approval · reason: drewstone_author · 2026-07-31T06:47:55Z
tangletools
left a comment
There was a problem hiding this comment.
🟢 Value Audit — sound
| Verdict | sound |
| Concerns | 1 (1 low) |
| Heuristic | 0.0s |
| Duplication | 0.0s |
| Interrogation | 125.8s (2 bridge agents) |
| Total | 125.8s |
💰 Value — sound
Forced migration: MCP server headers stop inlining Bearer <token> and now carry tagged secret-refs/public-config per agent-interface 0.38.0 — done correctly, reusing the real schema validator and the existing deterministic capability token, with no existing equivalent to duplicate.
- What it does:
buildHttpMcpServer/buildAppToolMcpServer/buildScopedMcpServerEntry/buildAppToolMcpServersand the per-domain wrappers now taketokenEnvKey(the NAME of a box-env variable) instead oftoken(a value). The emittedAuthorizationheader isdefineAgentProfileSecretRef(key,'bearer'); every other header isdefineAgentProfilePublicConfig(...).AppToolMcpServer.headerswidens from `Reco - Goals it achieves: (1) Stop leaking the capability token through profile material — an AgentProfile is digested, diffed, stored, and logged, so
Bearer <token>riding it is a credential leak by construction; agent-interface 0.38.0 retired exactly this shape. (2) Make the package actually compile: origin/main does not typecheck against the now-pinned agent-interface 0.40.0 (the removed cast becomes TS2352). (3) Kill - Assessment: This is a forced upstream migration, not an optional refactor, and it is executed in the grain of the codebase. The design decisions are all the right ones: it validates against the REAL shipped schema instead of re-implementing its rules (which would drift — 0.36→0.40 in three days, per the comment at src/tools/mcp.ts:94-108); it reuses the existing
createCapabilityToken(src/tools/capability.t - Better / existing approach: none — this is the right approach. Searched for an existing equivalent:
rg defineAgentProfileSecretRef|AgentProfileConfigValue src/returns matches ONLY in the new src/tools/mcp.ts, so nothing in the codebase already emits tagged MCP config that this could extend instead. The alternative of keeping a plain-stringtokenbranch was considered and correctly rejected (it re-admits the leak the ups - Model: opencode/zai-coding-plan/glm-5.2
- Bridge attempts: 2
- Bridge warning: opencode/kimi-for-coding/k2p7: bridge stream ended without value-audit content
🎯 Usefulness — sound
Forced alignment with agent-interface 0.38.0's tagged-config contract: the three MCP builders now emit secret-refs instead of inlined tokens, validated against the real schema, with the credential channel moved to box-env key names.
- Integration: Fully reachable — buildHttpMcpServer/buildAppToolMcpServer/buildScopedMcpServerEntry are the SOLE producers of MCP server entries in this package, consumed by buildAppToolMcpServers (src/sandbox/index.ts:482, the main provisioning path), buildSequencesMcpServerEntry (src/sequences/mcp-entry.ts:24), buildDesignCanvasMcpServerEntry (src/design-canvas/mcp-entry.ts:24), and the surface-overlay mechani
- Fit with existing patterns: Matches the codebase grain exactly. This is forced work: the PR body proves baseline origin/main fails TS2352 against agent-interface 0.40.0 (the removed
as AgentProfileMcpServercast), so the package was emitting a shape the schema rejects. The design validates each entry against the REAL agentProfileMcpServerSchema (src/tools/mcp.ts:109) rather than re-implementing the contract's rules — the a - Real-world viability: Robust across error paths. assertSecretEnvKey (mcp.ts:82) catches a token value passed where a key belongs; assertProfileMcpServer (mcp.ts:109) catches version skew, credential-bearing custom header names, relative URLs, and Bearer bytes — all fail-loud at build time. The deterministic-token requirement is documented AND tested: surface-profile.test.ts:86-91 proves createCapabilityToken is idempot
- Model: opencode/zai-coding-plan/glm-5.2
- Bridge attempts: 1
🔎 Heuristic Signals
🟡 Cruft: commented out code src/sandbox/index.test.ts
- // let the pre-0.38.0 plain-string shape ship. Validating the returned map
What this audit checks
It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.
| Pass | What it asks |
|---|---|
| Heuristic | Vague title? Whitespace-only or cruft-bearing diff? (content signals only) |
| Duplication | Do added function/class names already exist elsewhere in the repo? |
| Value Audit | What does it do? What goal does it achieve? Is it good? Better architecture or already-exists? |
| Usefulness Audit | Does it integrate and fit? Will it hold up in real use and actually get used? |
Findings are concerns, not blocks — the human reviewer decides what to do with them.
✅ No Blockers —
|
tangletools
left a comment
There was a problem hiding this comment.
✅ Approved — 8 non-blocking findings — 5e2108c7
Full multi-shot audit completed 8/8 planned shots over 22 changed files. Global verifier still owns final merge decision.
Full immutable report for this review: trace
Summary comment for this run: full summary
tangletools · 2026-07-31T07:07:35Z · immutable trace
Fixes #354. Breaking — warrants 0.46.0. Do not publish before reading "Ship order" below.
What was wrong
buildHttpMcpServerinlined the capability token as a plain string (Authorization: Bearer <token>).agent-interfaceretired that shape at 0.38.0:args,env, andheadersvalues must now beAgentProfileConfigValue— eitherdefineAgentProfilePublicConfig(value)ordefineAgentProfileSecretRef(key, format?). Profiles are digested, diffed, stored and logged, so a credential riding one as public material is a leak by construction.Because
/toolsis consumed by 10 of 11 products, the substrate everyone adopts emitted exactly the shape the contract rejects — and every consumer that bumped had to hand-write an adapter. gtm-agent already did (89 lines) after its production chat went down for ~25 days.Independent proof the cast was hiding a real break
Baseline
origin/main+ agent-interface 0.40.0 + zero source changes:That is the
as AgentProfileMcpServercast named in the issue. It is removed here.The design decision that matters
buildHttpMcpServernow takestokenEnvKey— the name of a box environment variable — never a token value. A secret-ref resolves only inside the private prepared executor, and the value channel onto a box is the create payload'senv.I deliberately did not widen
SandboxRuntimeConfig.envto the tagged type, contrary to item 4 in the issue. That would be a category error: the sandbox SDK types the create payloadenvasRecord<string,string>in both 0.15.2 and the 0.38.0-based 0.15.3 prerelease, andassertEnvWithinLimitsmeasures those bytes against the kernel'sMAX_ARG_STRLEN. The env map is the private resolution source — tagging it would make it reference itself. Documented on the seam instead.Because box env is written once and shared across turns, only a deterministic value is referenceable.
createCapabilityTokenis a bare HMAC, so the value written at creation equals what any later turn mints; a test pins that property.I did not port gtm's
withReferencedAuthorization. It exists solely to rewrite this package's bad output — tagging at the source makes it dead code, and shipping it would preserve a plain-string path.unresolvableSurfaceCredentialis contributed down from gtm, because naming the per-(user, resource) credential blocker is genuinely reusable.Verification
Real validators, real payloads, both versions:
Baseline vs branch, both with
dist/built — identical 18-file failing list, no regression:All 157 pre-existing failures are environmental on this machine (
better-sqlite3native bindings unbuilt; the scaffolder install fails identically on cleanorigin/main).typecheckclean ·test:gates289 passed ·knipexit 0 ·buildexit 0 · merges cleanly into main.Break-restore proof (the new tests must be able to fail). Tagging reverted to plain strings with the schema check stubbed:
Restored: 5 passed.
A CI regression this introduced, found and fixed
origin/mainbuilds at CI's 8192 MB heap; this branch OOMs there. Isolated in a throwaway worktree: baseline + 0.40.0 + a one-line cast builds fine at 8192, so the cost is the source change, not the version bump —AgentProfileConfigValuereaches five entry points, so rollup-dts inlines agent-interface's schema graph into each. Measured: 8192ERR_WORKER_OUT_OF_MEMORY, 10240 green at 9.4 GB peak RSS. Raised the three workflows to 12288, which leaves ~4 GB headroom on a 16 GB runner, with the measurement recorded in the comment.Peer range: left at >=0.36.0 on purpose
It should be
>=0.38.0. Tightening it today makes the graph unsatisfiable:agent-runtime@0.109.2peers<0.37.0andagent-profile-materialize@0.9.3peers<0.38.0.The gap is covered without a broken install: every builder now runs the resolved
agentProfileMcpServerSchema, so a consumer on a pre-0.38.0 agent-interface fails loudly and by name at build time rather than silently pairing.Ship order — read before publishing
This branch makes agent-app contract-correct. It is not runnable end to end until the platform ships resolution, so publishing 0.46.0 alone would hand consumers a shape the deployed platform currently rejects.
Blocking, in order:
mainis on 0.38.0 (tangle-network/agent-dev-container#4524, with a measured truth table and a 60-second reproduction).headersverbatim into the harness config, so a ref object would land in the config file as-is. ADC's workspace copy does resolve (sandboxAgentProfileSecretProvider), so this is a publish gap, not a design gap.0.15.3-developalready deps 0.38.0; needs a stable release.Then agent-app 0.46.0, then consumers.
Unverified
No live sandbox probe from this branch. The claim about what the deployed platform accepts rests on the separate measured reproduction in ADC#4524, not on a request issued from here.