Skip to content

MCP: the four entry tools are marked alwaysLoad (#3898 D10) - #4113

Merged
erikdarlingdata merged 3 commits into
devfrom
feature/3898-p4-alwaysload
Sep 24, 2026
Merged

erikdarlingdata merged 3 commits into
devfrom
feature/3898-p4-alwaysload

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Refs #3898.

Why

This is Erik's ruling D10 on #3898. It says to mark exactly four MCP entry tools with anthropic/alwaysLoad in their _meta, on each product that has them. Claude Code then keeps these tools loaded when it defers the rest of the tool surface.

What changes

  • Verified the key against the Claude Code MCP docs (https://code.claude.com/docs/en/mcp.md), section "Exempt a server from deferral". The exact doc line: "An MCP server can also mark individual tools as always-loaded by including "anthropic/alwaysLoad": true in the tool's _meta object, which has the same effect for that tool only."
  • Verified how ModelContextProtocol.Core 2.2.0 sets it. McpServerToolCreateOptions.Meta is a System.Text.Json.Nodes.JsonObject. Its own XML doc says it "is used to seed the Tool.Meta property". Tool.Meta is a JsonObject too, confirmed by reflection on the installed package.
  • Added a small allow-list (AlwaysLoadedToolNames) inside WithGeminiCompatibleTools in PerformanceMonitor.Common/Mcp/McpSchemaCompat.cs. For a tool whose registered name is in the list, the registration now sets options.Meta = new JsonObject { ["anthropic/alwaysLoad"] = true }. No tool class was hand-edited.
  • The four names are list_servers, get_fleet_overview, analyze_server, and get_tool_guide. Darling registers all four tool classes that declare these names, so Darling serves all four with the meta key. Lite has no get_fleet_overview. That read is central-store-only, Darling only. So Lite serves the other three: list_servers, analyze_server, get_tool_guide.
  • No tool description changed and no budget ceiling changed. The existing McpToolsListBudgetTests (Darling and Lite) and McpSchemaCompatTests (Lite) still pass unmodified. Adding _meta to four tools' served payload left the per-class budget files and the Gemini-schema golden tests alone. No file needed a fix.

Test plan

There is a new test class on each product, McpAlwaysLoadedToolsTests. It builds the real served tool list through the same WithGeminiCompatibleTools registration path the host uses, with no running server needed. It asserts the _meta["anthropic/alwaysLoad"] = true key is present on exactly the expected names, and on no other tool.

  • PerformanceMonitor.Common builds with 0 warnings.
  • Lite.Tests builds with 0 warnings.
  • Targeted run of McpAlwaysLoadedToolsTests on Lite: 2 tests pass.
  • Targeted run of McpToolsListBudgetTests plus McpSchemaCompatTests on Lite: 8 tests pass.
  • Darling.Tests builds with 0 warnings.
  • Targeted run of McpAlwaysLoadedToolsTests on Darling: 2 tests pass.
  • Targeted run of McpToolsListBudgetTests on Darling: 5 tests pass.
  • Full Lite.Tests suite: 5268 total, 0 errors, 0 failed, 0 skipped.
  • Full Darling.Tests suite: 13326 total, 0 errors, 2 failed, 636 skipped. The skips are the live-Postgres and live-runtime classes. This lane did not stand up that rig. The 2 failures are the two DarlingInstallLocationTests that fail on a non-elevated run until DarlingInstallLocationTests: the #4052 checks pass when run non-elevated #4111 merges (it has since merged): ThePreLockWritableExtractionCheck_TrustsTheAccountTheLockItselfGranted_OnAnAlreadyLockedTree and TheInstallTreeLock_ClosesTheInheritedGrant_KeepsProtectedFiles_AndReportsWhatItCannotClose. Neither touches MCP registration or this diff.

Coordinator verification

  • The key is right. The Claude Code MCP docs say: "An MCP server can also mark individual tools as always-loaded by including "anthropic/alwaysLoad": true in the tool's _meta object, which has the same effect for that tool only." I checked the live page.
  • One hardening edit (84ed0bd). The _meta JsonObject was built once per tool at registration and shared by every service provider that built the tool. A JsonNode is mutable and belongs to one parent, so the singleton factory now builds a fresh one for each tool it creates.
  • After the edit, both products built with 0 warnings. The targeted classes passed: McpAlwaysLoadedToolsTests, McpSchemaCompat*, McpToolsListBudget* and McpToolGuideTests (Darling 17 of 17, Lite 18 of 18). CI runs the full suites before merge.
  • Security: this adds metadata only. It changes no tool's behavior, input, auth or output.
  • Merge conflict: after MCP tool schemas exclude DI services by type, closing a reflection race that leaked them into tools/list (issue-4075) #4110 (Lite budget test sometimes sees get_alert_history's dataService parameter in a full run #4075) landed, McpSchemaCompat.cs conflicted with dev in two places. I kept both sides. The AlwaysLoadedToolNames set and the per-tool Meta stay, and tool creation now uses dev's SchemaOptionsFor(services) instead of the shared GeminiCompatSchemaOptions. After the merge, both builds have 0 warnings. I ran the targeted classes: McpAlwaysLoadedToolsTests, the schema-compat and Lite budget test sometimes sees get_alert_history's dataService parameter in a full run #4075 race tests, the budget, unknown-argument and inventory pins, and McpToolGuide*. Darling ran 147 tests and Lite 73, with 0 failed.

Handoff

Nothing open. The coordinator moved the changelog entry into the buffer as 4113-reported.txt and reworded it to name the Darling-only tool.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC

erikdarlingdata and others added 2 commits September 23, 2026 23:07
list_servers, get_fleet_overview, analyze_server and get_tool_guide now
carry _meta["anthropic/alwaysLoad"] = true in tools/list, set through a
small allow-list in WithGeminiCompatibleTools so Claude Code keeps them
loaded when it defers the rest. Lite has no get_fleet_overview, so it
gets the other three. Pinned with a new test class on each product.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC
…e per registration

A JsonNode is mutable and belongs to one parent, so each McpServerTool now gets its own
JsonObject from the singleton factory instead of sharing one instance across every service
provider that builds the tool.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 24, 2026 03:16
@erikdarlingdata
erikdarlingdata enabled auto-merge (squash) September 24, 2026 03:16
…ider schema options

McpSchemaCompat.cs conflicted with #4110 (#4075). Both sides stay: the alwaysLoad name set and Meta, and
SchemaOptionsFor(services) in place of the shared GeminiCompatSchemaOptions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUU29PuGg9TUFBsACgZg2K
@erikdarlingdata
erikdarlingdata merged commit 162d907 into dev Sep 24, 2026
16 of 18 checks passed
@erikdarlingdata
erikdarlingdata deleted the feature/3898-p4-alwaysload branch September 24, 2026 04:13
erikdarlingdata added a commit that referenced this pull request Sep 24, 2026
* Add 48 buffered CHANGELOG entries in one splice

Adds 48 entries and 48 link refs (#4057, #4077, #4079, #4081, #4082, #4083, #4084, #4085, #4086, #4087, #4088, #4089, #4090, #4091, #4092, #4093, #4095, #4096, #4099, #4100, #4101, #4103, #4105, #4106, #4107, #4108, #4109, #4110, #4111, #4113, #4114, #4115, #4116, #4117, #4118, #4119, #4120, #4121, #4122, #4123, #4124, #4125, #4126, #4127, #4131, #4133, #4136, #4141). Each PR's entry was buffered, and this lands every entry whose PR was merged on origin/dev when it ran.

Entries found under a bare section line (none unless the old script ran again) move into the matching ### section, below the new entries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC

* Changelog splice: add #4137's entry, which merged before the splice but was missed

#4137 (pg_plan_capture reads csvlog) merged at 05:57Z with a CHANGELOG entry section in its
body. It was neither spliced nor listed as needing no entry. Its entry goes under Fixed, right
after #4136's, and #4136's last sentence now points to it instead of saying plan capture is
unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUU29PuGg9TUFBsACgZg2K

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

1 participant