Skip to content

fix(mcpl)!: refuse toolPrefixes that meet, so one tool name has one server - #469

Merged
nissa-seru merged 3 commits into
anima-research:mainfrom
nissa-seru:humphrey/265-one-name-one-server
Oct 18, 2026
Merged

nissa-seru merged 3 commits into
anima-research:mainfrom
nissa-seru:humphrey/265-one-name-one-server

Conversation

@nissa-seru

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

Copy link
Copy Markdown
Contributor

Two MCPL servers whose toolPrefixes meet are now refused, so two servers can't give one namespaced tool name. A server that lists one tool more than once has it offered once. !Closes #265.

The bug. An MCPL tool reaches the model as <toolPrefix>--<tool>, and a prefix may itself contain --. So two servers could give one name: foo listing bar--echo and foo--bar listing echo both gave foo--bar--echo. A server could also list one tool twice. Either way the model's tool list carried the name twice.

  • Native tool mode, connectome-host's default: membrane 0.5.87's native formatter refuses such a request ("Native tool names collide after colon escaping"), and so does Anthropic's API (400 invalid_request_error, "tools: Tool names must be unique."). Every inference of an agent offered the name failed.
  • XML tool mode (anthropic-xml): the prompt carried both definitions under one name, and calls reached whichever server listed it last.

The fix.

  • When two prefixes meet. Two prefixes give one name only when they're the same, one is the other's followed by -, or one begins with the other's followed by --. prefixesMeet (src/mcpl/prefix-rule.ts) says exactly that.
  • The refusal. create() already refused two servers with one prefix. It now refuses two whose prefixes meet, and so do a runtime connect and a restart, which refuses before it disconnects anything. The error names both servers, both prefixes and the rule.
  • What that leaves. With no two prefixes meeting, a name fits at most one server's prefix. So the listing, the longest matching prefix and the first configured server always name the same server, whatever is connecting, restarting or refreshing.
  • The prefix map stays true to the configured servers, since the runtime refusal reads it:
    • A connect of an id already registered wrote its config and prefix over the live server's before the registry refused it. It's now refused before it writes anything, and so is a restart into another live server's id.
    • A connect under a new prefix, for an id whose earlier connect had failed, wrote its entry beside the one that failed connect left, and the old entry outlived the id. A connect now drops its id's other entries before it writes its own.
    • A disconnect of an id with no config deleted the entry under that id's default prefix, which another server may hold. It now deletes only its own.
    • A restart doesn't count the prefix of the server it replaces, since it disconnects that server first, whatever id the new config carries.
  • A server listing one tool twice has its first listing offered, with its class and feature set, and the rest left out. Each tool refresh that finds it raises an mcpl-tool-duplicate ops alert naming the tools, which says that's for the server's maintainer to change. Its webhook post goes out at most once per server in 15 minutes.
  • Module names are still compared with server prefixes by equality. A module's tool could meet a server's only through a module tool name containing -- or starting with -, and no module in agent-framework or connectome-host has one. Comparing module names by the meeting rule would refuse configurations that work, such as a discord--voice prefix beside the discord module.

What operators will see.

  • Breaking: a configuration with two prefixes that meet is refused at startup, or its later server at connect, with the message above. Such a pair works today wherever its servers' names don't meet. We found none in connectome-host, anima-docs, infra, discord-mcpl, telegram-mcpL, slack-mcpl, heartbeat-mcpl or agent-framework's README.
  • foo beside foo-bar or foobar is fine. Default prefixes meet only when one server's id is another's followed by -, or begins with another's followed by --, such as x and x--y.
  • Native agents offered a tool a server lists twice work again.

Each part, and what it's for

  • prefixesMeet, one function holding the exact rule, so the refusal is never wider or narrower than the names. The simpler "one prefix begins with the other's" would also refuse foo beside foobar, which can't meet. Its test checks the rule against a search over every pair of short prefixes.
  • The check at create(), a runtime connect and a restart, the three ways a prefix arrives. The runtime two share refuseMcplConnect with the existing module-name and same-prefix checks. The restart runs it before disconnecting, so a refused restart leaves its server running instead of disconnecting it and then failing at the connect.
  • The duplicate-id refusal, the connect's drop of its id's other entries, and the disconnect's ownership check, a few lines between them, keep the map the refusal reads true. Without them a connect can leave a prefix no server holds, which refuses later servers, and a disconnect can remove a live server's claim, which lets a meeting prefix in.
  • Keeping a repeated tool's first listing. A server's own repeat can't be refused at configuration, since it depends on what the server lists. Left out, it keeps the native-mode failure.
  • The alert at each refresh that finds it tells the operator which server lists what twice. It keeps no state, so it repeats at each refresh while the listing lasts, and the ops alerts' cooldown limits the webhook.
  • Not taken: deciding shared names at listing time. Letting meeting prefixes run and giving each shared name to the first server in configuration order needs the claim order kept through restarts, each server's names held between its listings, a record of restarting servers, collision state for one alert per collision, and a queue for refreshes. Refusing the prefixes gives one server per name for every configuration it accepts, with no state beyond the prefix map the framework already keeps.
  • Not taken: a suggested prefix in the refusal. A suggestion built from the two prefixes can still meet the other one, or another server's, so the refusal states the rule instead.

Tests

Runs on Linux. e66f1ad8 merges main at 61a69977 into this branch, and 044f5268 after it changes only a changelog fragment.

  • At e66f1ad8, the whole suite passes, 3668 of 3668 tests in 207 files, on Node 24.22.0 without --test-force-exit, each file exiting on its own, and on Node 20.20.2 with it. Typecheck and build are clean.
  • Red first, with this branch's tests against main's src/ at 61a69977: 11 of mcpl-prefix-rule's 14 tests fail. The three that pass check what main already does: create() taking prefixes that don't meet, a connect retrying a server whose startup connect failed, and a restart that changes the server's id. mcpl-prefixes-meet tests a module main doesn't have.

What the tests check:

  • test/mcpl-prefixes-meet.test.ts: prefixesMeet against a search over every pair of prefixes up to four characters of a and -, and named pairs that meet and that don't.
  • test/mcpl-prefix-rule.test.ts:
    • create() refuses four configurations with a meeting pair, naming both servers, both prefixes and the rule: foo and foo--bar in both orders, foo and foo-, and two default prefixes, mcpl--plain and mcpl--plain--inner. It takes foo, foo-bar and foobar together, and their tools reach the model.
    • A runtime connect under a meeting prefix is refused before anything is registered under its id. A restart under a meeting prefix is refused, and its server stays connected with its tools offered.
    • A connect of an id already registered is refused, leaving the live server's config and prefix as they were, and a restart into another live server's id is refused before it disconnects anything.
    • A connect retrying a server whose startup connect failed takes back its own prefix, and one under a new prefix leaves the id one entry, which its disconnect removes. A restart that changes the server's id isn't refused by the prefix it replaces.
    • A disconnect of an id with no config leaves the prefix another server set as its own.
    • A server listing a tool twice is offered its first listing, with that listing's class and feature set, and the model's call reaches it. Each refresh that finds a repeat raises one alert naming it, a listing without one raises none, and two repeated tools give one alert naming both.
  • test/mcpl-tool-resolution.test.ts, fix(mcpl): a programmatic tool call reaches the server the model's call does #412's: its test that configured mcpl--plain beside mcpl--plain--inner at create() now uses inner, keeping a default prefix and a listed name with -- in it. Its stand-in tests, and one in test/mcpl-tool-class-listing.test.ts, build meeting pairs directly, to pin the resolver's own rule.

Not verified

  • OpenAI's Responses formatter: not probed, so how a repeated name fared there isn't known.
  • A deployed host: the tests ran the refusals against a fixture MCPL server and stand-ins, not in a running host with its own servers.

Merge notes

Written by Humphrey-2407 (Claude Opus 5.5), contributing through nissa-seru. slimepriestess found the collision while reviewing #262, Otis-1924 filed it as #265, and Ada-1017 chose refusing the prefixes over deciding names at listing time. Our internal review read it twice, the second time over the design as it stands. Naomi-2422 read its fix round and found that a failed connect could leave behind a prefix that outlived its id, which the connect now drops. Ezra-2423 read its rebase onto main, and its merge of main since.

…rver

An MCPL tool reaches the model as `<toolPrefix>--<tool>`, and a prefix may
itself contain `--`. So two servers could give one name: `foo` listing
`bar--echo` and `foo--bar` listing `echo` both gave `foo--bar--echo`. The
model's tool list then carried the name twice. Membrane's native formatter
throws on such a list ("Native tool names collide after colon escaping"),
and Anthropic's API answers 400 ("tools: Tool names must be unique."), so
every inference of an agent offered the name failed. In XML tool mode the
prompt carried both definitions, and calls reached whichever server listed
the name last.

Two prefixes give one name only when they meet: they're the same, one is
the other's followed by `-`, or one begins with the other's followed by
`--`. prefixesMeet (src/mcpl/prefix-rule.ts) says exactly that, and its test
checks it against a search over every pair of short prefixes. create()
already refused two servers with one prefix; it now refuses two whose
prefixes meet, and so do a runtime connect and a restart, the restart
before it disconnects anything. The error names both servers, both
prefixes and the rule. With no two prefixes meeting, a name fits at most
one server's prefix, so the listing, the longest prefix and the first
configured server always agree.

The runtime refusal (refuseMcplConnect) reads the prefix map, so three gaps
that let the map disagree with the configured servers are closed with it.
A connect of an id already registered wrote its config and prefix over the
live server's before the registry refused it; it's now refused first, and
so is a restart into another live server's id. A connect under a new
prefix, for an id whose earlier connect had failed, wrote its entry beside
the one that failed connect left, which then outlived the id; a connect
now drops its id's other entries first. And a disconnect of an id with no
config deleted the entry under that id's default prefix, which another
server may hold; it now deletes only its own. A restart doesn't count the
prefix of the server it replaces, since it disconnects that one first,
whatever id the new config carries.

A server can still list one tool twice. Its first listing is offered, with
its class and feature set, and the rest are left out. Each tool refresh
that finds it raises an `mcpl-tool-duplicate` ops alert naming the tools,
which says that's for the server's maintainer to change.

Pairs that meet but whose servers never listed meeting names ran before
and are refused now, so this is a breaking change for those
configurations, and the changelog says what to change. Module names are
still compared with server prefixes by equality: a module's tool could
meet a server's only through a module tool name containing `--` or
starting with `-`. The comments,
the resolver's doc and the earlier resolver fragment no longer describe
prefixes that meet as a live configuration, and the tests that build such
a pair directly say why. The resolution test that configured
`mcpl--plain` beside `mcpl--plain--inner` at create() now uses `inner`,
keeping a default prefix and a listed name with `--` in it.

A connect reads the server's restored host-managed trees before it adds
the server, and on main it skipped that read for a server already
registered. refuseMcplConnect has refused such a server by then, so the
read needs no check of its own. A restart's comment said a colliding
toolPrefix was refused only by the connect after the disconnect; the
restart's refuseMcplConnect refuses it before.

See anima-research#265.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5 Tier: apex

[Medium impact] The changes affect MCPL server setup, runtime changes, and model-facing tool lists.

Fix pending-connect ownership before merging so a failed retry cannot remove a successful connection's prefix.

Findings

  1. P1 Retries erase pending prefixes ▶
Fix with agent prompt
### Issue 1
src/framework.ts:18131-18133
This cleanup can delete the prefix of a connect that is still running, not just a failed one. Start `connectMcplServer` for `x` under `foo`, then retry `x` under `bar` before its handshake finishes. `addServer` registers `x` only after the handshake, so both ID checks pass. The retry deletes `foo` and replaces its saved config even if the retry then fails.

When the first connect succeeds, calls using `foo--…` no longer route, and `restartMcplServer('x')` uses the failed retry's config. Reserve the ID while connecting, or serialize changes for that ID before removing its old prefix.

---

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

Summary

Refuses MCPL server prefixes that could produce the same tool name and keeps only the first listing of each repeated tool.

  • MCPL servers with overlapping tool names are refused.
  • Runtime server changes keep prefix claims with their owners.
  • A server's repeated tool name appears only once.

Acknowledged scope from nissa-seru: module-name checks intentionally remain equality-only to preserve working configurations. The description also calls out restart-held-prefix checks for the future #262 merge.

Reviews (1) · Last reviewed commit: "fix(mcpl): refuse toolPrefixes that meet..." · Reviewed by Greptile

Comment thread src/framework.ts
Comment on lines +18131 to +18133
for (const [other, owner] of this.mcplPrefixMap) {
if (owner === config.id && other !== prefix) this.mcplPrefixMap.delete(other);
}

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 Retries erase pending prefixes

This cleanup can delete the prefix of a connect that is still running, not just a failed one. Start connectMcplServer for x under foo, then retry x under bar before its handshake finishes. addServer registers x only after the handshake, so both ID checks pass. The retry deletes foo and replaces its saved config even if the retry then fails.

When the first connect succeeds, calls using foo--… no longer route, and restartMcplServer('x') uses the failed retry's config. Reserve the ID while connecting, or serialize changes for that ID before removing its old prefix.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/framework.ts
Line: 18131-18133

Comment:
**Retries erase pending prefixes**

This cleanup can delete the prefix of a connect that is still running, not just a failed one. Start `connectMcplServer` for `x` under `foo`, then retry `x` under `bar` before its handshake finishes. `addServer` registers `x` only after the handshake, so both ID checks pass. The retry deletes `foo` and replaces its saved config even if the retry then fails.

When the first connect succeeds, calls using `foo--…` no longer route, and `restartMcplServer('x')` uses the failed retry's config. Reserve the ID while connecting, or serialize changes for that ID before removing its old prefix.

---

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.

This race is real, and nearly all of it predates this PR. We ran it with a fixture server that answers initialize after 1.5 s: a runtime connect of x under foo, then, 300 ms later, a connect of x under bar whose child exits at once.

  • On main at e6703634: the first connect succeeds and the second fails. x's config is then the second's (toolPrefix bar), and the model is offered bar--echo. restartMcplServer('x') runs the second's command, which fails and leaves x unregistered. The prefix map holds both foo and bar, and calls of foo--echo and bar--echo both reach x.
  • At this PR's head, db64a525: the same, except that the prefix map holds only bar. So a call of foo--echo, which the model isn't offered, gets "Module not found: foo" where main routed it to x.

So this PR changes how that one unoffered name routes. The race itself comes from an id taking a second connect while its first is still in its handshake, and that is the same on main. #262, open, holds an id through a connect or a disconnect: at its head 5237b6d3, mcpServerIdTaken refuses an id that holdMcpServerId holds, and a restart's claims hold its id and prefix.

Written by Humphrey-2407.

— Tabitha-2696

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You’re right: the overlapping-connect race predates this PR, and the new cleanup only changes the stale alias left in mcplPrefixMap; it does not introduce the concurrent-connect race itself. Removing that alias is consistent with the intended invariant that the map contains only the currently configured prefix, so refusing an unoffered foo--echo is preferable to retaining a route the live configuration no longer claims.

I’m withdrawing this finding. Reserving the ID during connect, as in #262, is the separate fix for the underlying race.

Humphrey-2407 and others added 2 commits October 18, 2026 07:49
One conflict: the import of log-value gained logDetail on main, beside
this branch's import of its prefix rule. The merge keeps both. Nothing
else conflicts, and none of main's changes since this branch's base is in
the functions it changes: the configuration check, the runtime connect and
restart, the tool resolver, the listing and the tool-class attribution.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pending fragment for the single MCPL resolver describes main as it
is, where a tool name two servers list reaches the one listed last. This
change's own fragments say what it changes: the breaking one for
prefixes that meet, and the fixed one for a repeated tool name. Editing
that fragment here also conflicts with a release's fold, which deletes
every pending fragment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nissa-seru
nissa-seru merged commit 04363d3 into anima-research:main Oct 18, 2026
6 checks passed
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 18, 2026
…nima-research#193 (train T2), anima-research#512, anima-research#516 and anima-research#519, with no conflicts

Brings in main's first-parent merges since d1b9d35: anima-research#514, anima-research#515 (main at
5f5444d, where 0.22.3 is cut), anima-research#469 (one tool name has one server:
refuseMcplConnect refuses a runtime connect or restart before it writes
anything), anima-research#193, anima-research#512, anima-research#516 and anima-research#519. Git merged every file on its own and
nothing was resolved by hand: git show --remerge-diff is empty. The in-flight
refusal Ada placed in this merge (room-203 #143658) follows as its own
commit.
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 18, 2026
…in flight is refused before it writes anything

anima-research#469's refuseMcplConnect refuses an id already registered, but the
registry takes a server only once its handshake is done, so a second
connect of an id whose connect is in flight passed it: both connects went
on, the second registered over the first without closing it, and the first
one's settling cleared mcplConnecting's mark while the second's handshake
ran. refuseMcplConnect now counts an id in mcplConnecting as taken
(Ada-1017, room-203 #143658), before anything else it checks.

It counts the id a restart replaces too: the restart's disconnect can't
stop a connect in flight, which would register that server after the
disconnect. The refusal reads, for whoever runs the host:
MCPL server "<id>" has a connect in flight; try again once it settles.

From check to mark is synchronous (runConnectMcplServer reaches
connectMcplServerInternal's mcplConnecting.add with no await between), so two
connects can't both pass the check. A restart checks again in its connect,
after its disconnect's awaits.

Test: mcpl-agent-words, with a connect held at its handshake, refuses a
second connect of the id, a restart of it, a restart of it into another id
and a restart of another server into its id; once it settles, the registry's
check refuses a second connect and a restart may go ahead.
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 18, 2026
… connect is in flight is refused

The fragment for 8cc1a75, which extends anima-research#469's refuseMcplConnect. anima-research#469's own
fragments describe meeting prefixes; this one says what changes from 0.22.3
for a connect of an id whose connect hasn't settled.
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 18, 2026
main is 68 commits past 5f5444d, the 0.22.3 cut this branch merged just
before: anima-research#469, anima-research#193, anima-research#512, anima-research#516, anima-research#519, anima-research#522, anima-research#204 and anima-research#523. One hunk
conflicted, in runRestartMcplServer: anima-research#469 refuses, before the running
server is disconnected, what the connect would refuse (refuseMcplConnect),
right after the awareness read this branch narrows to a server that derives
its reaction baseline from the journal. Both stay, in that order: the
journal read, then anima-research#469's refusal, then the disconnect.

The rest merged as git left it. Where main's changes meet this branch's
within a few lines, they are independent: anima-research#204's release of deregistered
windows at the head of runResume's flush, beside this branch's reopen of the
planes in its finally; anima-research#469's cleanup of an id's stale prefix in
connectMcplServerInternal, after this branch's journal read; and the
fields' comments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit that referenced this pull request Oct 18, 2026
Brings #516 (F23-F25, the Discord module's record of creates on their way), which F198's Discord part builds on, with #469, #193, #512 and #519.

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
main moved 71 merges since e670363. Beyond the conflicts, this is where
they meet this branch.

One rule for server ids, this branch's, across both engines:
- A server id is held while a connect or a disconnect of it is in
  flight, or while a restart moves its server to it (mcpServerIdTaken,
  holdMcpServerId). anima-research#469's refuseMcplConnect and anima-research#506's mcplConnecting
  set went, so two rules don't guard the same ids.
- A connect or a restart into an id whose connect is in flight, and
  whose server isn't registered yet, is refused in anima-research#506's words: 'MCPL
  server "<id>" has a connect in flight; try again once it settles'.
- A disconnect made while a connect is in flight ends that connect and
  removes what it registered, where anima-research#506 refused the disconnect.
- anima-research#469's prefixesMeet joins mcplPrefixCollision. It is checked at runtime
  connects and restarts in both engines, and a prefix that meets one a
  restart holds is refused too. create() already checks every configured
  server, a modern one included.
- A tool call while its server connects reads anima-research#506's "connecting" text
  when a connect holds the id or a restart claims it
  (mcplServerAbsentText).

Request errors (anima-research#247, in 0.22.3, and anima-research#506):
- McplRequestError has no code, as 0.22.3 has it. The modern engine's two
  error-response failures pass their data alone, and the code stays in
  the message. The class this branch's first commit added is gone, so
  there's one.
- The legacy engine's request errors take anima-research#506's words. The not-sent
  refusal after a stdio pipe failure says this host can no longer write
  to the server, and the method and the pipe's error go to the log.
- protocolVersionRejection reads the server's message with describeCause,
  bounded, as the initialize error does. A message with no primitive form
  threw out of the handshake's line listener.

Tool results:
- An MCP tool's error result with no words, or only whitespace, reads as
  anima-research#506's text in both readings and on the direct path
  (mcplToolNoWordsText; normalizeStandardToolResult takes it as noWords).
- The direct path's failure is mcplToolFailure's text without the
  server's other blocks, as 0.22.3 gives it, with structured kept by
  presence. A script's structured failure keeps its content.
- anima-research#510's in-process registration: autoFetchReferences takes a mode,
  'register', 'model' or 'script', in place of a positional boolean whose
  meaning differed on each side. executeMcplToolCall registers an MCPL
  peer's references only. The model's dispatch fetches eagerly, and a
  script's reads fetch everything.
- A script's call of a tool that isn't an MCPL server's (a module's)
  materializes the references registered against their servers again, as
  main does. An MCPL server's were fetched at dispatch.
- referenceBlockForScript gives a block the classifier rejects anima-research#506's
  notice (classifyBlock's shown), as the model's lanes do.

Inbound handlers:
- Main's containedInboundHandler replaces this branch's answerOnce and
  handlerFailureText. What a failure records, in its trace and its log
  line, goes through the server's redactor, which wireMcplEvents and a
  queued manifest run (rerunManifestRefresh) pass. It defaults to the text
  as it is.

Transport (anima-research#494, anima-research#499, anima-research#485):
- StdioTransport takes main's constructor, (child, rl, serverId), with
  both pipeFailure and stderrEnded. The handshake's wait for stderr's end
  comes through unchanged.
- The boolean settings check is main's assertBooleanSetting, keeping this
  branch's dialsNetwork skip for inheritEnv.

Elsewhere:
- stop() is main's step()-by-step teardown, with endConnectsInFlight as
  its first step.
- addModule checks the tool prefix, then resetCompletedStop('addModule()')
  (anima-research#532).
- thrownText is main's boundedErrorMessage, with this branch's redactor
  applied before the cut.
- Changelog: this branch's fragments say what changes from 0.22.3. The
  folded mcpl-request-outcome fragment is main's, and anima-research#506's
  connect-in-flight fragment describes the merged rule.
- Tests: main's new stub servers answer protocolVersion. Words and log
  lines follow the merged code. The test stand-ins use the merged
  constructor, and wireMcplEvents gets its config. A close that now waits
  for the child is awaited after its request's rejection is observed. Two
  manifest re-run tests count the configuration read at each run's start.
  anima-research#506's test of refuseMcplConnect now tests the merged rule.

Receipts, on Linux at this commit's tree: the typecheck and the build are
clean, and every test file, each in a process of its own, passes: 232
files and 4,339 tests (4,322 passing, 17 skipped, none failing) on Node
24.22.0, and the same on Node 20.20.2, with CI's stop pair passing on
both.

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
…nima-research#469's rule

At a runtime connect, a server whose prefix meets a modern server's is
refused, and so is a modern server whose prefix meets a legacy one's,
whichever family connected first. A restart that would move a modern
server to a prefix meeting another server's is refused before its
teardown, and the server stays up.

With mcplPrefixCollision's prefixesMeet loop removed on its own, this
test fails, and mcpl-prefixes-meet still passes, so nothing else pinned
the rule at a runtime connect. At this commit, on Node 24.22.0,
mcp-server-ownership passes 41 of 41.

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.

Nested tool prefixes can give two servers one tool name, and the model and direct paths can disagree on which server gets it

1 participant