Skip to content

fix(hire): warn when MCP grants cannot run on bundled engines - #320

Merged
PeterGuy326 merged 5 commits into
mainfrom
fix/314-243-hire-mcp-and-engine-copies
Sep 18, 2026
Merged

PeterGuy326 merged 5 commits into
mainfrom
fix/314-243-hire-mcp-and-engine-copies

Conversation

@waterbro-8

Copy link
Copy Markdown
Collaborator

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_unsupported and 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: turnStream group-engine membership now uses turnEngines instead of a handwritten subset, and Agent Host listing order is derived from the exhaustive HOST_DEFINITIONS catalog.

Validation

ID Check Status
V1 Renderer test capability-picker.test.tsx written; NOT VERIFIED here (no node_modules in this environment)
V2 apps/server/test/agent-registry.test.ts Host-order coverage extended; NOT VERIFIED locally
V3 Repository npm run check NOT VERIFIED — CI

No merge, release, or issue close.

waterbro-8 added a commit that referenced this pull request Sep 18, 2026
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.
waterbro-8 added a commit that referenced this pull request Sep 18, 2026
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.
@waterbro-8
waterbro-8 force-pushed the fix/314-243-hire-mcp-and-engine-copies branch from c363bda to 01b5cb9 Compare September 18, 2026 09:28
@waterbro-8

Copy link
Copy Markdown
Collaborator Author

Rebased onto current main (ecc0294). The only conflict was CHANGELOG.md vs #325/#297 entries; kept both.

This remains the CapabilityPicker warning follow-up for #314 (does not close the issue). Server-half is #319.

Head: 01b5cb9.

@waterbro-8

Copy link
Copy Markdown
Collaborator Author

Hosted CI on 01b5cb9 failed renderer vite build: importing turnEngines from @roleweave/shared pulled packages/shared/dist/position-id.js (createRequire) into the browser graph.

Head e42e749 restores the renderer-local engine set. Picker warning + tests unchanged. CI re-running.

waterbro-8 added a commit that referenced this pull request Sep 18, 2026
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.
@waterbro-8
waterbro-8 force-pushed the fix/314-243-hire-mcp-and-engine-copies branch from e42e749 to 7311492 Compare September 18, 2026 09:55
@waterbro-8

Copy link
Copy Markdown
Collaborator Author

Rebased onto current main abb8bcc (#330). Head 7311492. Previous Node 24 rerun was green; CI re-running after rebase. Still does not close #314.

@PeterGuy326 PeterGuy326 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Hire and profile editors now surface that every bundled engine rejects
employee-level MCP on the first turn. Group SSE engine membership and
agent-host order are derived from the shared contract so a new engine
cannot silently drop out of those copies.

Refs #314
Refs #243
Importing turnEngines from @roleweave/shared pulled packages/shared
position-id.js (createRequire) into the Vite renderer graph and failed
hosted package builds. Restore the renderer-local engine set.

Refs #314
Refs #243
`value in HOST_DEFINITIONS` is true for inherited Object keys such as
constructor, so a malformed agentEngine id could pass the guard and
then throw when reading capabilities. Own-property check fails closed.

Refs #314
Refs #243
@waterbro-8
waterbro-8 force-pushed the fix/314-243-hire-mcp-and-engine-copies branch from 7311492 to e8ca20f Compare September 18, 2026 10:13
@waterbro-8

Copy link
Copy Markdown
Collaborator Author

Addressed the fail-closed regression on isKnownHostId: value in HOST_DEFINITIONS became Object.hasOwn(HOST_DEFINITIONS, value). Added a boundary test that constructor / toString / hasOwnProperty yield empty capabilities instead of throwing.

Rebased onto current main (includes #337). Head e8ca20f. CI re-running.

@waterbro-8

Copy link
Copy Markdown
Collaborator Author

Hosted CI on e8ca20f is green after the Object.hasOwn fail-closed fix (Node 24 ubuntu + macos, installers, smoke, CodeQL, Scorecard).

PeterGuy326: the in HOST_DEFINITIONS prototype-chain regression is the item from your CHANGES_REQUESTED on 7311492. Please re-review this head. I cannot self-approve.

Still does not close #314.

PeterGuy326 added a commit that referenced this pull request Sep 18, 2026
…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>
@waterbro-8

Copy link
Copy Markdown
Collaborator Author

#319 landed on main (a131efc). This branch was BEHIND; merged current main in (GitHub update-branch). New head 9c337ba.

CI is re-running. Object.hasOwn fail-closed + picker warning unchanged. Still does not close #314. PeterGuy326: please re-review when CI is green. I cannot self-approve.

agentHostSupportsEmployeeMcp("constructor") === false (also toString / hasOwnProperty).
Object.hasOwn already rejects prototype-chain keys; this pins the hire/profile
gate so a malformed engine id stays 422 instead of throwing.

Refs #314
Refs #243
@waterbro-8

Copy link
Copy Markdown
Collaborator Author

Per 胡奕舟: the fail-closed gate now has the exact pin agentHostSupportsEmployeeMcp("constructor") === false (also toString / hasOwnProperty). isKnownHostId remains Object.hasOwn(HOST_DEFINITIONS, value).

Head e5bf02f. CI re-running. Does not close #314.

Please re-review this head; I cannot self-approve.

@waterbro-8

Copy link
Copy Markdown
Collaborator Author

Node 24 / macos-14 timed out on unrelated renderer/test/App.test.tsx (keeps the employee workbench mounted…, 5000ms). ubuntu on this head was green. Treating as flake: reran the failed verify job only. Head remains e5bf02f (no new commit).

@PeterGuy326 PeterGuy326 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants