Skip to content

refactor(mcp): answer info and runtime reads through their owners - #2335

Merged
ScriptedAlchemy merged 2 commits into
masterfrom
fleet/mcp-typed-info-health
Sep 27, 2026
Merged

ScriptedAlchemy merged 2 commits into
masterfrom
fleet/mcp-typed-info-health

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Owner

What changed

The info and health tools that still went through LegacyToolCompatibilityOwner and the generic dispatch fallback now run through typed owners:

  • Graph-tool owner (project): tracedecay_status, tracedecay_active_project, tracedecay_remote_status, tracedecay_runtime.
  • Profile owner (daemon, every connection): tracedecay_project_list, tracedecay_project_search, tracedecay_project_context. They travel as a new ProfileGraphTool invocation (InvocationTarget::Profile). The handshake's project, if there is one, only marks which listing entry is active.

Each tool decodes its arguments into a typed request (deny_unknown_fields) and returns a typed catalog result (project_info_surface.rs) that serializes to the JSON these tools already emitted. An unknown or mistyped argument is now refused by the owner, not silently ignored.

status still carries wait_for (#2179, and the structuredContent.wait from #2314 that the CLI uses for its exit code), memory (#2194/#2220, including the #2301 holder shape), github_source (#2221/#2260), reset_required_stores, and the restoring freshness state from #2293.

Deleted:

  • The hand-written projectless project_* path in daemon/projectless.rs, now folded into the profile owner.
  • The per-server project_registry_reads port.
  • ProjectRegistryListingOutcome and the RegistryUnavailable variants.
  • dispatch_groups/health_dispatch.rs and the Health dispatch group.
  • The server_stats/scope_prefix parameters of handle_tool_call*. The owner reads them from the serving server.

JsonSchema derives added: the remote-status types (remote/status.rs), doctor sources (LanguageServerReadV1, RemoteListenerReadV1, the language-server state and analyzer enums, ResidentMemoryHolderReadV1), DoctorReportV1, TableGrowthDoctorEvidenceV1, the domain remote authority types, AuthorityEpoch, and StoreResetRequiredV1. Contracts and the TypeScript SDK are regenerated.

Compat symbols are left in place for the deletion PR. Sibling tools are untouched.

Proof

Fail before / pass after through the real MCP server (mcp_suite info_health_request_test). On origin/master, status {"include_diagnostics": true} answered markdown and the test failed. Now each tool refuses with its owner's problem record, for example:

{"kind":"invalid_request","code":"application.surface.invalid_request","message":"invalid arguments for tracedecay_status: unknown field \include_diagnostics`, expected one of `admission_only`, `include_branch_diagnostics`, `include_storage_health`, `include_session_ingest`, `include_staleness`, `wait_for`"}`

The same holds for active_project {"project"}, remote_status {"kind"}, runtime {"doctor"}, project_list {"limt"} and {"limit":2.5}, project_search {"query":12}, and project_context {"project_selector":{"project":…}}.

The projectless socket test gained a refused call. With the mark_semantic_tool_error fix removed it fails (isError: null), and it passes with the fix.

Journey on the built binary (isolated HOME, one daemon under systemd-run --user --scope -p MemoryMax=6G -p MemorySwapMax=1G, stopped afterwards):

  • tracedecay serve tools/list: 230.
  • status: admission_only returns project_admitted: true; include_diagnostics returns isError: true.
  • active_project: proj_…, active_project, code_project.
  • remote_status: {"kind":"unconfigured"}.
  • project_list: ok, with the project marked is_active: true; limt returns isError: true.
  • project_context: ok, is_active: true.
  • runtime {"doctor_report":true}: doctor report kind: observed, keys [kind, language_servers, report, schema_convergences, table_growth_evidence], report keys [coverage, entries]; doctor returns isError: true.
  • A full status serves memory owners in the perf(code-index): share one decode across linked worktrees #2301 holder shape.

Counts (last runs, on the final base):

  • lib tests: tracedecay-mcp 387, contracts 420, daemon-protocol 65, daemon-service 322, domain 223, mcp-catalog 29, tool-catalog 6, api 53.
  • mcp_suite filtered to status/project/runtime/remote/info_health/admin/protocol/dashboard/graph_analysis/port_status/automation/analytics: 206 passed. The one failure (a process-sample timeout at load average ~150) passes alone.
  • Root lib subset (handlers, profile, projectless, multi-root, server, application_surface): 193.
  • CLI: bins 332 plus the new wait-outcome unit test; core_cli_suite tool_first_touch/tool_daemon: 34.
  • clippy -D warnings is clean with and without test-transport; cargo fmt is clean; contracts:check is up to date.

Reviewer notes

  • status is no longer a compat tool, so the CLI tests that probe the compat transport now use multi_root_scope_set_read with an absent id. Its concealed not_found_or_not_authorized answer only comes from an admitted project server. The fix(cli): type readiness waits and projectless refusals end to end #2314 fake-daemon wait-outcome test became a unit test of tool_result_process_outcome; the owner's structuredContent.wait is pinned in dispatch_tests.
  • Projectless-routed refusals carry problem at the top level, like the existing retained projectless path, rather than in structuredContent. That is pre-existing behavior of the projectless route, not changed here.

tracedecay_status, tracedecay_active_project, tracedecay_remote_status
and tracedecay_runtime are graph-tool operations the project's graph-tool
owner answers, and tracedecay_project_list, tracedecay_project_search and
tracedecay_project_context are profile registry reads the daemon's
profile owner answers for every connection, with or without a project.
Each decodes its arguments against a typed request, so an unknown key or
a wrong type is refused instead of ignored, and each result is a typed
catalog result that serializes to the JSON these tools already emitted.

The projectless connection's hand-written registry path is folded into
the profile owner, and the per-server registry read port is gone. The
doctor and remote-status types derive JsonSchema. A route refusal from
an owner now keeps its reason code instead of reading as a config error.
@changeset-bot

changeset-bot Bot commented Sep 27, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 01daa78

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@ScriptedAlchemy
ScriptedAlchemy merged commit 7842e01 into master Sep 27, 2026
1 check passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-27T13:58:16.577139Z 01daa78 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ScriptedAlchemy
ScriptedAlchemy deleted the fleet/mcp-typed-info-health branch September 27, 2026 13:50

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 01daa780ed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +55 to +59
#[serde(untagged)]
pub enum StatusResultV1 {
Admission(StatusAdmissionV1),
Project(Box<ProjectStatusV1>),
}

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 Badge Add a typed project-opening status outcome

When the first tracedecay_status request arrives while project opening is incomplete, serve_core_doctor_runtime_request returns project_open_status_value, which contains project_open, uses schema_convergence.status = "unavailable", and omits required ProjectStatusV1 fields such as memory and code_index_freshness. Neither variant of this new untagged result accepts that routine startup response, so the advertised/generated result contract cannot decode an actual production status result; add a typed project-opening variant or translate the core response into the canonical shape.

AGENTS.md reference: AGENTS.md:L9-L12

Useful? React with 👍 / 👎.

Comment on lines +352 to +354
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeResultV1 {

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 Badge Type the daemon-core runtime outcome

For tracedecay_runtime startup-health/full-doctor requests, and for doctor_report requests before the owner report is ready, the daemon core returns doctor_runtime_value directly. That payload omits required fields such as captured_at and host_os, adds doctor_runtime, project_open, and git_watcher, and its fallback doctor report has a reason while omitting fields required by RuntimeDoctorReportV1::Unknown; consequently this new deny_unknown_fields result type and generated clients reject a production response for the same catalog operation. The core path needs its own typed variant or normalization into RuntimeResultV1.

AGENTS.md reference: AGENTS.md:L9-L12

Useful? React with 👍 / 👎.

Some(cancellation),
)
.await?;
let response_handle_root = cli_response_handle_root(profile, dispatch.project_path.as_deref())?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid reopening project layout for registry CLI reads

When a profile-registry CLI read names an initialized project whose repository identity marker is malformed or unreadable, the daemon profile owner can successfully answer from the registry, but this new post-response call re-reads that project marker through resolve_persisted_layout and replaces the valid result with a local error. Registry list/search/context are profile-owned and previously rendered projectlessly without a response-handle root—listing rendering still explicitly ignores the root—so these calls should render with no project layout lookup rather than reacquiring an unrelated project authority.

Useful? React with 👍 / 👎.

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