fix(hire): warn when MCP grants cannot run on bundled engines - #320
Conversation
Every bundled engine spawns with an empty MCP config, so a granted binding survived hire and then failed every turn at spawn time (qoder.mcp_binding_unsupported), away from its cause (#314). Gate the grant at the two surfaces that accept it: - POST /hire returns 422 hire_mcp_unsupported before staging or the engine validator sees the request. - PATCH /positions/:id/profile rejects a well-formed grant with 400 position_profile_invalid while keeping the empty-grant clearing path open for pre-existing packages. Both gates read the agent registry instead of a hardcoded list, so a host that ships MCP support unlocks its own grants with no further edits here. The renderer maps the new failure code to explicit zh/en copy. Refs #314. Does not close #314. CapabilityPicker disable / persistent warning is follow-up #320.
Every bundled engine spawns with an empty MCP config, so a granted binding survived hire and then failed every turn at spawn time (qoder.mcp_binding_unsupported), away from its cause (#314). Gate the grant at the two surfaces that accept it: - POST /hire returns 422 hire_mcp_unsupported before staging or the engine validator sees the request. - PATCH /positions/:id/profile rejects a well-formed grant with 400 position_profile_invalid while keeping the empty-grant clearing path open for pre-existing packages. Both gates read the agent registry instead of a hardcoded list, so a host that ships MCP support unlocks its own grants with no further edits here. The renderer maps the new failure code to explicit zh/en copy. Refs #314. Picker disable / persistent warning remains on follow-up PR #320; this change is the server-half only.
c363bda to
01b5cb9
Compare
|
Hosted CI on Head |
Every bundled engine spawns with an empty MCP config, so a granted binding survived hire and then failed every turn at spawn time (qoder.mcp_binding_unsupported), away from its cause (#314). Gate the grant at the two surfaces that accept it: - POST /hire returns 422 hire_mcp_unsupported before staging or the engine validator sees the request. - PATCH /positions/:id/profile rejects a well-formed grant with 400 position_profile_invalid while keeping the empty-grant clearing path open for pre-existing packages. Both gates read the agent registry instead of a hardcoded list, so a host that ships MCP support unlocks its own grants with no further edits here. The renderer maps the new failure code to explicit zh/en copy. Refs #314. Picker disable / persistent warning remains on follow-up PR #320; this change is the server-half only.
e42e749 to
7311492
Compare
PeterGuy326
left a comment
There was a problem hiding this comment.
Request changes on current head 7311492.
The picker warning and the derived HOST_ORDER are the right shape: the warning only appears with a bound MCP server, the derived catalog keeps registration order identical to the previous handwritten list (qoder first, workbuddy last), and #243's silent-omission class is closed.
One fail-closed regression to fix before merge: isKnownHostId switched from HOST_ORDER.includes(value) to value in HOST_DEFINITIONS. The in operator walks the prototype chain, so well-known inherited keys such as "constructor", "toString", or "hasOwnProperty" now pass the guard. Downstream HOST_DEFINITIONS[hostId].capabilities then returns undefined and .includes("mcp") throws a TypeError — a malformed but string-typed agentEngine id turns a fail-closed 422 into an unhandled 500. This matters because #319's agentHostSupportsEmployeeMcp feeds user-controlled ids straight into this guard, and its own test promises "unknown or malformed ids fail closed rather than throwing".
Suggested fix: use an own-property check, e.g. typeof value === "string" && Object.hasOwn(HOST_DEFINITIONS, value) (or build a Set of ids), plus a boundary test asserting agentHostSupportsEmployeeMcp("constructor") === false and getAgentHostCapabilities stays silent for inherited-key ids.
7311492 to
e8ca20f
Compare
|
Addressed the fail-closed regression on Rebased onto current main (includes #337). Head |
|
Hosted CI on PeterGuy326: the Still does not close #314. |
…or (#319) Every bundled engine spawns with an empty MCP config, so a granted binding survived hire and then failed every turn at spawn time (qoder.mcp_binding_unsupported), away from its cause (#314). Gate the grant at the two surfaces that accept it: - POST /hire returns 422 hire_mcp_unsupported before staging or the engine validator sees the request. - PATCH /positions/:id/profile rejects a well-formed grant with 400 position_profile_invalid while keeping the empty-grant clearing path open for pre-existing packages. Both gates read the agent registry instead of a hardcoded list, so a host that ships MCP support unlocks its own grants with no further edits here. The renderer maps the new failure code to explicit zh/en copy. Refs #314. Picker disable / persistent warning remains on follow-up PR #320; this change is the server-half only. Co-authored-by: waterbro-8 <318569545+waterbro-8@users.noreply.github.com> Co-authored-by: 修雨 <47820304+PeterGuy326@users.noreply.github.com>
|
Per 胡奕舟: the fail-closed gate now has the exact pin Head Please re-review this head; I cannot self-approve. |
|
Node 24 / macos-14 timed out on unrelated |
PeterGuy326
left a comment
There was a problem hiding this comment.
Re-review approve on head e5bf02f.
Object.hasOwn closes the prototype-chain gap, and the new boundary tests pin the fail-closed contract for inherited keys (constructor / toString / hasOwnProperty) on both getAgentHostCapabilities and agentHostSupportsEmployeeMcp. The derived HOST_ORDER keeps the previous registration order. This also satisfies the picker acceptance that #319's option (b) deferred here; #314 can close with this landing.
Refs #314
Refs #243
Summary
Hire/edit capability pickers currently let an operator bind MCP connectors. Every bundled engine then fails the first turn with
qoder.mcp_binding_unsupportedand nothing at grant time says so.This change keeps the grant (engines may grow into it) but fails closed on the UX: binding any MCP server shows a status warning naming that error code.
#243:
turnStreamgroup-engine membership now usesturnEnginesinstead of a handwritten subset, and Agent Host listing order is derived from the exhaustiveHOST_DEFINITIONScatalog.Validation
capability-picker.test.tsxapps/server/test/agent-registry.test.tsHost-order coveragenpm run checkNo merge, release, or issue close.