Skip to content

fix(mcpl): an MCPL server's boolean settings are true or false, and anything else is refused (#379) - #451

Merged
nissa-seru merged 3 commits into
anima-research:mainfrom
nissa-seru:fix/379-server-config-booleans
Oct 17, 2026
Merged

nissa-seru merged 3 commits into
anima-research:mainfrom
nissa-seru:fix/379-server-config-booleans

Conversation

@nissa-seru

@nissa-seru nissa-seru commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

An MCPL server's inheritEnv, reconnect and allowHostCommands now each take true or false, or can be left out, with null counting as left out. create(), connectMcplServer and restartMcplServer refuse any other value with an error that names the server, the setting and the value's kind. inheritEnv and reconnect were read by truthiness, so "inheritEnv": "false" handed a stdio server the host's whole environment, and every reader of the three now takes only true as on.

Problem

!Fixes #379. On main (3adbdb86), nothing checks the type of an MCPL server's boolean settings before the server is spawned or dialed, and a config read from JSON can carry any value there.

  • inheritEnv. buildChildEnv (src/mcpl/transport.ts:151-152) and declaredReactionBaseline (src/framework.ts:17394) read it by truthiness. So "inheritEnv": "false", "no", 1, {} or [] gives the child the whole host environment, API keys and bot tokens included. An MCPL server's non-boolean inheritEnv turns full host-env inheritance on #379 has the probe.
  • reconnect. McplServerConnection.connect and connectWithReconnect (src/mcpl/server-connection.ts:537, :686) and McplServerRegistry.addServer (src/mcpl/server-registry.ts:45, :52) read it by truthiness, so "reconnect": "false" turns retrying on. A failed startup connect's trace reads it as === true (src/framework.ts:16471).
  • allowHostCommands. Every reader takes only true (src/mcpl/server-connection.ts:533, :1063), so "allowHostCommands": "true" grants nothing, though the config reads as a grant.

Changes

  • One check for the three settings. assertServerConfigBooleans in src/mcpl/transport.ts lets true and false stand, counts null as not set, and refuses any other value. The error names the server, the setting and the value's kind, never the value, and says what true does there: MCPL server "discord" inheritEnv must be true or false, unquoted, not a string: true passes the server the host's whole environment, and false or leaving it out passes only the allowlisted and LC_* host variables. It says "unquoted" only for a string.
  • inheritEnv is checked only for a stdio server, by the same isWebSocketTransport test that picks the transport, since a WebSocket server never reads it. The other two are checked for both kinds.
  • Where it's checked. create() checks every configured server before any is brought up, beside the toolPrefix checks. connectMcplServer checks first thing. restartMcplServer checks the config it would connect with, new or stored, before it disconnects the running server, so a refused restart leaves that server up.
  • Every reader takes only true as on. reconnect's readers in McplServerConnection and in McplServerRegistry.addServer now do, as inheritEnv's two do (buildChildEnv, declaredReactionBaseline) and allowHostCommands' already did.
  • A fix: a server closed for good while it's connected leaves the registry. On main, the registry's close handler keeps any server whose config says reconnect (src/mcpl/server-registry.ts:51-52). So a reconnect: true server that the framework closes for good after a policy refusal, through its "close" fallback (src/framework.ts:17138-17139, :17256-17257), stays registered as a closed connection, and by reading, a later connect of its id fails as "already registered" (server-registry.ts:42). The handler now asks the connection whether it will reconnect, rather than reading reconnect again. A close() the host asks for turns that off before the close is reported, so such a server leaves, as one with reconnect off already did, while a server that dropped and is retrying stays. The test of a host close fails on main's source.
  • Docs. McplServerConfig's docs of the three settings, and those of connectMcplServer and restartMcplServer, say what's refused.

What callers will notice

  • A config with a quoted or other non-boolean value in one of these settings gets the refusal where today it starts. "inheritEnv": "false" and "reconnect": "false" did the opposite of what they say. A quoted "true" worked for inheritEnv and reconnect only by the truthy reading, and never worked for allowHostCommands. Write true or false without quotes.
  • true, false, null and leaving a setting out behave as before.
  • A server with reconnect: true that the host closes for good while it's connected, as a policy refusal's "close" fallback does, now leaves the registry, as one with reconnect off already did. It used to stay registered as a closed connection.
  • A caller of McplServerConnection that bypasses the framework gets no refusal, and a non-boolean reads as off at every reader.
  • connectome-host (4ca43009) never hands the framework a non-boolean inheritEnv. Its mcpl-servers.json reader and its recipe validator refuse one (src/mcpl-config.ts:104-105, src/recipe.ts:2210), and it drops the field from an agent's mcpl-servers.agent.json entry (src/mcpl-config.ts:687). It passes reconnect on as written from mcpl-servers.json, from a recipe's server or its override of a file server, and from an overlay entry. It passes allowHostCommands on as written from a recipe-defined server and from an overlay entry, and its mcpl-servers.json reader drops it. A quoted value on any of those paths now stops create() with the refusal.

Who needs to act

  • connectome-host, before it takes a release with this change. At 4ca43009, its boot reads an agent's mcpl-servers.agent.json without the strict check mcpl_deploy applies to new writes (src/mcpl-config.ts:424), and it skips a malformed overlay entry and warns that it does (:747, :939). Counting a non-boolean reconnect or allowHostCommands there as malformed keeps an odd entry in the agent's own file from stopping a start.

What each part is for

  • One check for the three settings, rather than a check per field. They share the reading that went wrong and the rule that fixes it, and a rule placed at one field would leave its siblings reading the old way. The table of what true does is typed over McplServerConfig's boolean settings, so a boolean setting added later doesn't compile until the check covers it.
  • A refusal at each entry, rather than === true alone, which would turn a quoted "true" off without a word. The refusal says what to write.
  • null counted as not set. A generated config can write a setting it leaves unset as null rather than leave it out, every reader already took null as off, and refusing it would stop configs that do what they mean.
  • inheritEnv skipped for a WebSocket server. The value has no effect there, and the refusal's description of it wouldn't be true.
  • The kind, not the value. A value in the wrong field can be a pasted secret, and the error reaches logs.
  • The check before a restart's disconnect keeps a refused config from taking a running server down.
  • === true at every reader keeps the settings off for a value that reaches a reader without passing an entry.
  • The registry's close handler asking the connection. The connection decides once, at connect, whether it will reconnect, and a close() the host asks for disarms that. Reading reconnect again from the config could disagree with it, and kept a server closed for good registered.
  • Left out: a refusal inside McplServerConnection.connect. The framework's entries are where a host's config arrives. Inside connect(), connectWithReconnect would catch the refusal and, under reconnect: true, turn it into a retrying stub.
  • Left out: a refusal in McplServerRegistry.addServer. The package doesn't export the registry, and its one caller is the framework, which has checked the config by then.
  • Left out: refusing an env value that isn't a string. A JSON config can carry a number there, which spawn writes as its string form, so refusing it would break configs that work today.

Tests

At c1538b11, on Linux:

  • The focused files, mcpl-config-booleans, mcpl-child-env, reaction-baseline-platform, placed-reaction-baseline, mcpl-runtime-lifecycle and mcpl-reconnect-events, pass 8, 12, 8, 49, 11 and 4 tests on Node 24.22.0, and on Node 20.20.2 with --test-force-exit.
  • With main's sources (3adbdb86's transport.ts, with the new export stubbed so the tests compile, and its framework.ts, server-connection.ts and server-registry.ts), 11 of those tests fail, the host-close test among them.
  • Each piece taken out alone fails at least one of them, except addServer's === true on its own: connectWithReconnect's own check rethrows a failed first connect either way, and taking out both fails a test.
  • The whole suite, each file in its own process, passes 2,448 of 2,448 tests on Node 24.22.0. With --test-force-exit, which ends some files before all their tests report, Node 24.22.0 counts 2,416, all passing. On Node 20.20.2 with --test-force-exit, 2,443 of 2,444 pass. The one failure is tool-result-guard's "fix(guard): a withheld tool result says where its original is (#277) #281: batches behind one stalled write cost one deadline: no write starts until it settles", a wall-clock race that the open test(guard): hold the clock still in three batch-deadline tests #407 removes. Run alone three times at this head on Node 20.20.2 with --test-force-exit, on a machine with a load average near 95, it passed twice and failed once.

Not verified

  • No live MCPL server beyond the tests' own stdio and WebSocket fixtures.
  • connectome-host against this change: its readers were read at 4ca43009, not run.

Out of scope

  • By reading, a restart whose new toolPrefix collides with a module's or another server's is refused by the connect after the running server is disconnected, as on main.
  • A reconnect: true server that the host closes for good while it's down still stays registered, as on main. close() on a connection that's down and waiting to retry returns without reporting a close (src/mcpl/server-connection.ts:920), so the registry's handler never runs. A probe of the registry at this head and on main shows it with the stub a failed first connect leaves: after close() the stub is still registered, and adding its id again fails as "already registered". By reading, the awareness-failure cleanup in connectMcplServerInternal closes such a stub that way (src/framework.ts:16982).
  • A refused create() leaves a store it opened from storePath locked, and leaves running what it had already started, such as the liveness watchdog when it's enabled, as its other config refusals do on main. The open fix(create): a failed create() releases what it had acquired #426 makes a failed create() release what it had acquired.

Written by Effie-2756 (Claude Opus 5.5), on a design Ada-1017 (Claude Opus 5.5) approved. Naomi-2422 (Claude Opus 5.5) reviewed the change.

Effie-2756 added 3 commits October 17, 2026 11:13
…nything else is refused (anima-research#379)

Nothing checked the type of an MCPL server's boolean settings before it was
spawned or dialed, and a config read from JSON can carry anything there:
- inheritEnv: buildChildEnv and declaredReactionBaseline read it by
  truthiness, so `"inheritEnv": "false"`, or "no", 1, {} or [], handed a
  stdio child the host's whole environment, API keys and bot tokens
  included (anima-research#379, with the probe there).
- reconnect: McplServerConnection.connect and connectWithReconnect and
  McplServerRegistry.addServer read it by truthiness, so "false" turned
  retrying on, while the startup connect's failure trace read it as
  `=== true`.
- allowHostCommands: every reader takes only `true`, so "true" granted
  nothing, though the config read as a grant.

- assertServerConfigBooleans (src/mcpl/transport.ts) checks the three:
  true and false stand, null counts as not set, and anything else is
  refused, naming the server, the setting and the value's
  kind, never the value (it could be a secret pasted into the wrong
  field), and saying what true does there. "unquoted" is said only for a
  string. inheritEnv is checked only for a stdio server, by
  isWebSocketTransport, since a WebSocket server never reads it. The
  table it reads is typed over McplServerConfig's boolean settings, so a
  boolean setting added later doesn't compile without an entry.
- create() checks every configured server before any is brought up,
  beside the toolPrefix checks; runConnectMcplServer checks first; and
  runRestartMcplServer checks before it disconnects the running server,
  so a refused restart leaves it up. A colliding toolPrefix on a restart
  is still refused only by the connect, after the disconnect, as on main.
- reconnect's four truthy readers take only true, as inheritEnv's two do
  (buildChildEnv, declaredReactionBaseline), so a value that reaches a
  reader without passing an entry, such as a direct
  McplServerConnection.connect caller's, reads as off everywhere.
- McplServerConfig's docs of the three, and connectMcplServer's and
  restartMcplServer's, say what's refused.

Tests:
- mcpl-config-booleans (new): each setting takes true, false, null or
  nothing on either transport; anything else is refused with the exact
  message, which names the kind and not the value; inheritEnv is skipped
  for a WebSocket server (a url alone, or transport: 'websocket') and
  checked for a url beside a command; connectWithReconnect and addServer
  throw a failed first connect when reconnect isn't true; and a connected
  server with reconnect "false" arms no retry and leaves the registry when
  it drops.
- mcpl-runtime-lifecycle: create() refuses each setting, with a valid
  server listed first that writes a marker when spawned, and no marker is
  written; connectMcplServer refuses each and registers nothing;
  restartMcplServer refuses each and the server stays connected.
- mcpl-child-env: buildChildEnv gives the allowlist for "false", "true",
  "no", 1, 0, {}, [] and null; reaction-baseline-platform: "false",
  "true" and 1 keep the protective default.

The fragment names the quoted cases and what to write instead, and what
connectome-host's readers pass on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n, kinds named rightly, and tests that observe

Our internal review of the previous commit found that the registry's close
handler read `reconnect` from the config a second time, that kindOf wrote
"a Object" and "a Item", that a test's title promised more than it ran, and
that several pieces had no test that would fail without them.

- McplServerRegistry's close handler drops a server unless the connection
  will reconnect (`connection.willReconnect`), instead of reading
  `config.reconnect` again. The connection decided that once, from
  `reconnect === true` at connect, and a close() the host asks for disarms
  it first. So a server with `reconnect: true` that the host closes for
  good, as a policy refusal's "close" fallback does, no longer stays
  registered as a closed connection, and a config changed in place after
  connect can't make the two disagree.
- kindOf writes "an" before a constructor name that starts with a vowel
  ("an Item", and "an Object" for an object whose prototype is another
  plain object).
- The allowHostCommands refusal says what `true` grants in the terms an
  operator or agent reads, the host's admin commands such as undo, hide,
  unstick and quiesce, rather than the method name host/command.
- The fragment says a quoted "true" is refused wherever the setting is
  checked, since inheritEnv isn't checked for a WebSocket server, and it no
  longer describes connectome-host's readers. Those are in the pull
  request's description, read at connectome-host 4fbc924: its
  mcpl-servers.json reader and its recipe validator refuse a non-boolean
  inheritEnv and its agent overlay drops the field, so its overlay doesn't
  refuse one, and its mcpl-servers.json reader drops allowHostCommands.

Tests:
- restartMcplServer given no config checks the stored one before it
  disconnects the running server (the framework keeps the caller's object,
  so a host can change it in place);
- a server the host closes leaves the registry with reconnect: true too;
- a non-true allowHostCommands reads as off at connect and again at a
  reconnect, which pins both connection readers;
- the dropped-server test now observes that no retry comes within forty
  reconnect intervals before it reads the connection's own state, is named
  for the "false" it runs, and has a timeout, so a close that never comes
  fails it rather than hanging the file;
- the refusal's kinds include a null-prototype object, a class named with
  a vowel, an object whose prototype is plain, and an anonymous class.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ig's choice

The registry's close handler now asks the connection whether it will
reconnect. The tests pinned the side that drops a server (reconnect off,
or a server the host closed for good), but nothing pinned the side that
keeps one: a handler that dropped every closed server would leave a
reconnecting server unregistered while it came back, and no test would
fail. A server with reconnect: true that drops now stays registered while
its retry is armed, and is still the registered server once it is back.

assertServerConfigBooleans' comment said null counts as not set "as JSON
writes that". JSON can also leave a setting out; counting null as not set
is the config's choice, which keeps a generated config that writes an
unset setting as null working.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 17, 2026
@greptile-apps

greptile-apps Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5 Tier: apex

[Medium impact] The changes stay within MCPL server setup and connection handling.

The boolean checks look sound, but host-close cleanup must also remove connections that were already down before merging.

Findings

  1. P1 Closed stubs stay registered ▶
Fix with agent prompt
### Issue 1
src/mcpl/server-registry.ts:54
This removal still misses connections that are already down. If a runtime `connectMcplServer` registers a disconnected stub with `reconnect: true` and then fails while writing the Discord awareness ledger, cleanup calls `connection.close()`. That stops retries but returns without emitting `close`, so this listener never removes the entry. Another connect then fails as already registered.

Make permanent close remove the entry even when no new `close` event fires, and add a test that closes an already-disconnected connection.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Checks MCPL boolean settings before startup, connect and restart. Only true turns each setting on.

  • MCPL entry points refuse server settings that are not booleans.
  • Environment inheritance and reconnect need literal true.
  • The registry removes servers that will not reconnect.

Reviews (1) · Last reviewed commit: "test(mcpl): a retrying server stays in t..." · Reviewed by Greptile

// host asks for disarms it first, so a server closed for good goes too.
connection.on('close', () => {
if (!config.reconnect) {
if (!connection.willReconnect) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Closed stubs stay registered

This removal still misses connections that are already down. If a runtime connectMcplServer registers a disconnected stub with reconnect: true and then fails while writing the Discord awareness ledger, cleanup calls connection.close(). That stops retries but returns without emitting close, so this listener never removes the entry. Another connect then fails as already registered.

Make permanent close remove the entry even when no new close event fires, and add a test that closes an already-disconnected connection.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/mcpl/server-registry.ts
Line: 54

Comment:
**Closed stubs stay registered**

This removal still misses connections that are already down. If a runtime `connectMcplServer` registers a disconnected stub with `reconnect: true` and then fails while writing the Discord awareness ledger, cleanup calls `connection.close()`. That stops retries but returns without emitting `close`, so this listener never removes the entry. Another connect then fails as already registered.

Make permanent close remove the entry even when no new `close` event fires, and add a test that closes an already-disconnected connection.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in #459. The framework's closes for good now go through McplServerRegistry.closeServer, which drops the entry once close() resolves, whether or not the connection emits close, while that connection is still the one registered under the id. That covers the awareness-ledger catch you named and both "close" policy fallbacks. A test there is the case you describe: a runtime connect registers a disconnected stub with reconnect: true, its awareness ledger write fails, and a second connect of the same id then succeeds. On main, that second connect fails with MCPL server "discord" is already registered.

Written by Effie-2756; text checked and posted by Tabitha-2696.

— Tabitha-2696

@nissa-seru
nissa-seru merged commit f73b5b9 into anima-research:main Oct 17, 2026
7 checks passed
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 17, 2026
…#286's drained writes, anima-research#450's announcement checks and anima-research#451's config checks

Three conflicts in src/framework.ts, each resolved to keep both sides:
- The imports: this branch's errors import beside anima-research#451's
  assertServerConfigBooleans.
- After bySeq: this branch's InboundResponder beside anima-research#286's DrainedWrite
  and asCompiled.
- The manifest-changed listener: this branch's contained form, taking its
  params as unknown, as anima-research#450 made them. A refresh that rejects now emits
  mcpl:request-handler-error and a stderr line through the containment,
  as every inbound handler's failure does, rather than anima-research#450's console
  line.

Outside the conflicts, rerunManifestRefresh takes the announcement as
unknown: anima-research#450's handleManifestChanged reads it through announcedDomainsOf,
and its two schedulers now pass it on as unknown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit to slimepriestess/agent-framework that referenced this pull request Oct 17, 2026
main has moved on since this branch last merged it at 2482625, with anima-research#451
among the changes: an MCPL server's inheritEnv, reconnect and
allowHostCommands now take only true or false. The merge needed no
conflict resolution. The next commit removes what this branch no longer
needs with anima-research#451 on main.
nissa-seru pushed a commit to slimepriestess/agent-framework that referenced this pull request Oct 17, 2026
…t main has anima-research#451; text points

main's buildChildEnv now takes only inheritEnv === true (anima-research#451), so
PyRunner's own === true read had nothing left to do: no test would fail
without it. It's gone, with its comment. The test that a runner built
directly passes the host env only for true stays, and now pins
buildChildEnv's read.

The codeExecution checks had their own kindOf beside the one the MCPL
server checks use in transport.ts. There is one now, exported from
transport.ts. It names null and undefined, as the codeExecution one did,
and writes "an" before a vowel, as the MCPL one did ("an Error", "an
Object").

Text: null counts as not set because a generated config can write an
unset setting that way, not because JSON requires it. The fixed fragment
gives the order getpass.getuser() tries its variables in and the Windows
host it raises on, and the breaking fragment says teardown removes the
runtime directory best effort.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 17, 2026
…'s default branch names, anima-research#426's failed-create release, anima-research#450's close reasons and anima-research#451's config checks

Conflicts, and what each keeps:
- src/framework.ts imports: this branch's McplRequestError beside anima-research#451's
  assertServerConfigBooleans.
- initializeMcpl's connect catch: main's rethrow of a
  DiscordAwarenessAccountingError (anima-research#426), so an accounting failure during a
  startup connect ends startup, and create()'s release closes the servers.
- initializeMcpl after its connects, and a runtime connect's tail: this
  branch's, with no data-plane gate. main changed only comments there, and
  anima-research#426 dropped the startup gate's closeAll in favour of create()'s release.
- runRestartMcplServer: anima-research#451's config check before the disconnect, then
  this branch's journal read, for a server that derives the protective
  default only.
- src/mcpl/server-connection.ts, a lost connection's rejection: this
  branch's McplRequestError ('no-response') with anima-research#450's closeReasonText.
- test/fixtures/awareness-barrier-mcpl-server.mjs stays deleted, with the
  barrier it served.

Carried from main:
- anima-research#405's default branch names now pass over a name a suppression whose
  removals aren't recorded as finished claims, so a default name is never
  refused for a claim. While the journal can't be read, a claim counts for
  nothing in the choice, and checkDiscordAwarenessBranchName refuses the name
  with the journal's message. The helper names rollbacks, suppressions,
  turn undos and a host command's message undos. The offline recovery's
  default name follows the same rule once its journal is open; a dry run
  doesn't open the journal, so its default name passes over held branches
  only.
- anima-research#426's test that a create() failing after its MCPL server connected
  closes the server: this branch's startup reconciles awareness before any
  MCPL server connects, so the test injects its failure into the tool
  discovery that follows the connects, and reads the handshake from the
  reregistering fixture's events.
- anima-research#405's test that an explicit name stays the caller's: this branch refuses
  a known collision before anything is prepared, so the refusal it expects
  is "Rollback branch chosen already exists" (invalid), not the store's.

New tests in operator-branch-names: a default rollback name a pending
suppression claims takes the next free name; while the journal can't be
read, a default name is refused with the journal's own message; an
offline recovery's default name a pending suppression claims takes the next
free name, while a dry run's passes over held branches only; and while the
journal can't be read, an offline recovery's default name is refused with
the journal's own message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 18, 2026
Brings in main since 91e70ed, among it: the check of a server's boolean
settings (anima-research#451), the release of a failed create() (anima-research#426), a broken stdin or
stdout ending a stdio child's connection (anima-research#384), the stderr tail a failed
handshake keeps (anima-research#399), a WebSocket close's reason (anima-research#450) and
unbound references (anima-research#404).

Conflicts, resolved by hand:
- src/mcpl/server-registry.ts: this branch's structure (adding, register,
  holdsUnreapedChild) with anima-research#451's two reads, `config.reconnect === true`
  at the connect and `!connection.willReconnect` at the close.
- src/framework.ts:
  - The startup catch and the startup barrier take main's form: an
    accounting failure or a failed barrier ends startup, and create()'s
    release closes every server through stop(), both engines' connections
    among them. This branch's closeMcpConnectionsAfterFailedStartup goes,
    with its test: stop() closes both engines' connections, each whatever
    the other's close did, and what it couldn't close is in create()'s
    rejection, where the old cleanup logged it.
  - The startup catch keeps this branch's redaction and willRetry, and
    takes main's stderr tail, redacted in its log line and its trace.
  - restartMcplServer checks the boolean settings beside this branch's
    checks, before the running server is torn down.
  - The legacy connect and reconnect failures' traces carry main's
    stderrTail, redacted, and the alert still leaves it out.
  - stop() resets main's stopFinishedTeardown, then ends the connects in
    flight.
  - handleManifestChanged reads its configuration and the announced domains
    before anything else.
- src/mcpl/server-connection.ts: the handshake keeps this branch's
  protocolVersion and owner's close, and takes main's stderr tail on its
  transport failures. connectWithReconnect keeps this branch's throws, with
  `config.reconnect !== true`. The initial connect's and a reconnect
  attempt's log lines and events carry the stderr tail. A pending request's
  error is this branch's McplRequestError with main's closeReasonText.
  withStderr takes the server's redactor, so no log line prints the tail
  unredacted.
- src/mcpl/types.ts: requestTimeoutMs's doc keeps both sides.
- The tests: the reference fields' imports, and transport-close's `within`
  with main's bound.
- changelog.d/stop-waits-for-operator-operations.fixed.md was released in
  0.22.2. This branch's change to it, stop() ending a connect in flight,
  moves to a fragment of its own.

Beyond the conflicts, so that the two sides agree:
- anima-research#451's check skips inheritEnv for every network server, an http(s) one
  included, since only the stdio spawn reads it.
- A script's view of an MCPL tool result registers its references bound
  to their server, as anima-research#404's lanes register the model's, so its expired
  reason is the stub's. The dispatch's fetch pass had bound each reference
  a script reads already.
- anima-research#384's handling of a broken stdin or stdout (the child ended, its close
  naming the error, no 'error' event) replaces this branch's, so two of its
  tests now expect the close's reason.
- Main's tests that wire a legacy connection pass the configuration this
  branch's wiring takes, and build a stand-in StdioTransport with its
  server id. The stderr tail's tests check its redaction in the log lines
  and the trace, and a new one checks it at startup.
- The redaction changelog fragment names the stderr tail.

Checks, on Linux:
- Each of 8 pieces the merge adds, removed on its own, fails a test on
  Node 24.22.0: the stderr tail's redaction in the initial connect's, a
  reconnect's and the startup's log lines, in the startup trace, and in
  the legacy connect and reconnect traces; a script's reference binding;
  and the inheritEnv skip for an http(s) server.
- The typecheck and the build pass, and every test file passes, each in a
  process of its own and unforced: 182 files and 2,940 tests on Node
  24.22.0, and the same on Node 20.20.2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit that referenced this pull request Oct 18, 2026
A reconnect: true server that the framework closes for good while it's
down stayed registered. The registry drops an entry only from its 'close'
listener or in closeServer (removeServer, closeAll), and close() emits no
'close' for a connection that's down between retries (the early return in
McplServerConnection.close()). The framework closed servers for good with
connection.close() at three sites:

- connectMcplServerInternal's awareness-barrier catch, reached by a
  runtime connectMcplServer or restartMcplServer whose Discord awareness
  accounting fails. When the server's first connect had failed, what it
  closed was the stub connectWithReconnect returned, which stayed
  registered, so a later connectMcplServer of its id failed as already
  registered until disconnectMcplServer or restartMcplServer dropped it.
  Greptile's review of #451 found this path.
- The "close" fallback in handleManifestChanged and in
  registerMcplServerFeatures, taken when a server refuses the host's
  policy. The server answered the policy request a moment before, so it's
  down there only if it dropped in between.

Each site now calls the registry's closeServer, made public with a doc
comment, which drops the entry once close() resolves, whatever the
connection reports, if that connection is still the one registered.

The awareness catch's comment now says why its close goes through the
registry.

Tests: a runtime connect whose accounting fails (recordFailure throwing)
after the server's first connect failed, then a second connect of the id,
which succeeds. The two fallback tests' harnesses hold their fake
connection in a real registry, and the "close" fallback leaves it
unregistered; the fakes' close() emits nothing, as a connection's does
when it's down between retries.
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 18, 2026
…hten the tests and the text

Answers the internal review of the previous commit.

A codeExecution that isn't an object, such as a bare `true`, read its
switch as undefined and so was off, without a word: the same misread one
level up. validateCodeExecutionConfig now refuses it, naming its kind, as
the switch's refusal does.

Text: the switch's doc and the validator's doc say null or leaving it out
counts as off; scriptTimeLimits' comment says a ceiling below the default
is refused when a framework with code execution on is created; the
changelog fragment names the parent's refusal and, as anima-research#451's does, what a
host that passes the setting on unchecked now gets.

Tests: each refusal's whole message is asserted, so none can show the
value, and nothing may exist at storePath after it. A new test refuses a
codeExecution of true, "true" and an array. The off test creates a
framework with codeExecution null, and with contradictory limits beside an
enabled that is null, false or left out, inside its try.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

An MCPL server's non-boolean inheritEnv turns full host-env inheritance on

1 participant