sleuth_mcp 0.8.0
sleuth_mcp: ^0.8.0 copied to clipboard
MCP stdio sidecar exposing ext.sleuth.* VM service extensions to AI clients.
0.8.0 #
Pins sleuth 0.37.0 and accepts 0.36 apps with a warning; 0.35 apps are
refused. The SDK floor is Dart ^3.8.0, and vm_service widens to
>=14.3.1 <16.0.0. The
full release notes
list every change.
Changed #
get_snapshotreturns a compact default of seven sections and lists the others in_omittedSections. On an iPhone 12 that was 10 KB instead of 285 KB.full: truereturns every section.compare_snapshotsrefuses snapshots from different sleuth lineages, from the warm-up, or with different VM coverage, and compares issues per stableId (countChanged).check_budgetsandsleuth_checkrefuse withcoverage_degraded(sleuth_checkexit code 2) when the app has no VM link.check_budgetsthresholds default to thesleuth_checkvalues.get_issuesno longer reports a connectedbasicsession as degraded, andconnectreturnsvmConnected.- An app
packageVersionmust be semvermajor.minor.patch; anything else is refused withversion_skew_unknown. - Tool descriptions,
instructions, advisories and error messages are rewritten as plain sentences. Error-code prefixes are unchanged.
Added #
get_logsreturns the app's recentprint, stderr anddart:developerlines (the sidecar keeps the last 500).attach_appreports progress and can be cancelled.app_statusreportsconnectedandconnectedVia, andinitializereturnsinstructions.connectandattach_app(debugUrl:)accept the http URI thatflutter runprints.
Fixed #
- With
--uri,hot_reloadno longer freezes the sidecar. - A tool timeout keeps the connection. After 8 unanswered calls the sidecar
answers
app_busyuntil the next connect. - The sidecar follows a hot restart and reports
session_changedonce. detach_appworks in every state. A failed stdout write or a stalled flutter stdin no longer crashes the sidecar, and exit takes at most about 12 s.connectrefuses withattached_sessionwhile anattach_appsession owns the connection.- The sidecar kills
flutter devices,xcrun devicectlanddns-sdcommands on cancel or timeout, on Windows withtaskkill /T. hot_reloadreturnshot_reload_unsupportedorhot_reload_failedinstead of reporting success.- JSON-RPC batches are accepted only on
2025-03-26sessions.
0.7.2 #
Launch-mode advisory. connect, attach_app, diagnose, get_snapshot, and
get_issues stamp an optional launchModeAdvisory string when the app's
connectionMode shows VM-only detectors are degraded, nudging a
flutter run --profile --no-dds relaunch. Stamping the data tools (not just the
connection tools) means a client reading issues never gets a degraded payload
without the warning attached:
basic+vmConnected == false→ VM-only detectors (heap_growing, heavy_compute, excessive_repaint, gc_pressure, stream_resource) suppressed; no VM self-connect (usually DDS claiming it). VM-connectedbasic(verdict still warming) fires nothing.warmup→ connectionMode not final; re-rundiagnose.disconnected→ no live VM connection.full/correlated→ none.
Separate from the version-skew warning; both can coexist. Top-level on
connect / attach_app; inside data on diagnose / get_snapshot /
get_issues; app_status omits it (no bridge call). Malformed connectionMode
/ vmConnected degrade to no advisory (never throws). Sidecar pin sleuth 0.36.0
unchanged.
0.7.1 #
Fuller tool annotations. Sidecar-only; pin sleuth 0.36.0 unchanged.
- Every tool descriptor now carries the full MCP annotation set —
destructiveHint,idempotentHint,openWorldHint— alongside the existingreadOnlyHint.openWorldHint:truefor tools whose output reflects the live app or host (get_snapshot,get_issues,get_route_health,diagnose,check_budgets,list_devices);falsefor self-contained tools (explain_issue,compare_snapshots,app_status).detach_appisdestructiveHint:true(it tears down the session and deletes disk-handoff files). Read-only tools omit destructive/idempotent (the spec ignores those unless the tool is not read-only). - The three new hints are locked per tool in
doc/mcp_tool_schema.json(toolAnnotations) and enforced by the audit;readOnlyHintstays sourced fromreadOnlyTools. Tool-schemaschemaVersionstays2(advisory descriptor metadata, not a return-shape change).sleuthMcpVersion0.7.0 → 0.7.1. - Advisory hints for annotation-aware clients; no behavior change.
0.7.0 #
MCP prompts — guided diagnostics. Sidecar-only; pin sleuth 0.36.0 unchanged.
- Adds the
promptscapability +prompts/listandprompts/getmethods. Three argument-free prompt templates instruct an MCP client's model to chain the existing tools:triage_performance(snapshot → top issues → explain → worst route),audit_memory(memory-class issues → explain → remediations),release_check(budgets + critical issues → PASS/FAIL). - Prompts are static (no bridge, no per-session state).
prompts/geton an unknown name returnsinvalidParams. - A test cross-checks each prompt's referenced tool names against the live tool registry both ways, so a renamed tool can't silently rot a prompt.
sleuthMcpVersion0.6.6 → 0.7.0.
0.6.6 #
Structured tool output (MCP 2025-06-18). Sidecar-only; pin sleuth 0.36.0
unchanged.
- For clients that negotiate protocol
2025-06-18or later, every successtools/callresult now carries a top-levelstructuredContentfield — the same JSON the text content block carries, as an object — so clients consume it without re-parsing the text. Clients on2024-11-05/2025-03-26receive the text content only (additive, backward-compatible). structuredContentmirrors the entire tool result (the full envelope for passthrough tools, the file pointer fordiskHandoff), and is never set on error results.outputSchemaper-tool declarations are not included in this release.- Tool-schema doc
schemaVersionstays2(no per-tool shape changed).sleuthMcpVersion0.6.5 → 0.6.6.
0.6.5 #
Compact-issue projection for get_issues + get_snapshot. Sidecar-only;
pin sleuth 0.36.0 unchanged.
get_issuesis now compact by default: each issue is trimmed to the actionable subset (severity,category,confidence,title,detail,fixHint,stableId,widgetName,routeName,sourceRoute,confidenceReason,rootCauseIds) and the list is capped to the top 50 of the app's already-ranked order. Passverbose: truefor the full ~22-field shape;maxIssueCountchanges the cap (0= unbounded; negative is rejected witharg_invalid_int, for parity withget_snapshot). When the cap drops issues,datacarries_truncated: true+_totalCount(post-filter, pre-cap count). The cap andverboseare orthogonal —verbosecontrols field shape only.get_snapshottrims eachdata.currentIssuesentry to the same compact subset unlessverbose: true. Applies on both the inline anddiskHandoffpaths from a fresh map. No-op whencurrentIssuesis projected out viasections. The lib-sidemaxIssueCount/maxRouteCountcaps are unchanged.- Compaction keeps
stableId+severity, socompare_snapshotsandcheck_budgetsstill operate on compact snapshots. - Tool-schema doc
schemaVersion1 → 2.sleuthMcpVersion0.6.4 → 0.6.5.
0.6.4 #
Pin sleuth 0.36.0. Sidecar-only.
sleuthPackageVersionPin0.35.0 → 0.36.0 for theSleuthConfig.showOverlayrelease;acceptedPriorLineages{0.34} → {0.35}.sleuthMcpVersion0.6.3 → 0.6.4.
0.6.3 #
Tool readOnlyHint annotations. Sidecar-only; pins sleuth 0.35.0.
- Every tool descriptor carries an MCP
annotations.readOnlyHint:truefor the read-only tools,falsefor the four that mutate connection/app state (connect,attach_app,detach_app,hot_reload). Annotation-aware MCP clients can auto-approve reads instead of prompting per call. readOnlyToolslocked indoc/mcp_tool_schema.json, enforced by the schema audit.sleuthMcpVersion0.6.2 → 0.6.3.
0.6.2 #
iOS attach ambiguous-pairings recovery. Sidecar-only; pins sleuth 0.35.0.
- When a stale dead VM-service port and a fresh live port coexist in the
iOS mDNS cache after a relaunch (each with a distinct authCode),
attach_appno longer refuses withios_ambiguous_pairings. It connects to each announced authCode in turn and keeps the one whose VM service is live (the dead port resets). A user-suppliedauthOverridestill pins a single service (no iteration). All candidates dead, or 3+ coexisting records, fall back toios_vmservice_busy(swipe the app and re-run). sleuthMcpVersion0.6.1 → 0.6.2.
0.6.1 #
iOS attach mDNS-staleness auto-recovery. Sidecar-only; pins sleuth 0.35.0.
attach_app(udid:, bundle:)now recovers automatically when the iOS mDNS cache pins a dead VM-service port from a prior session. On abridge.connectfailure that looks like a dead device port (Connection reset— iproxy resets the tunnel when the device refuses the channel — orConnection refused), the attach retries once, re-resolving Bonjour with the dead port excluded so selection lands on the live service, which announces a different port within the same staleness window. Falls through to a plain relaunch only if no live port is announced. No manualdevicectlrelaunch needed.- Wireless-transport failures and half-open VM services (connect timeout) are not recovered — re-resolving can't help those.
IosAttacher.attachgainsexcludePortsand bounds eachxcrun devicectlcall with adevicectlTimeout(default 20s), so a disconnected device or stalled device-services can't hang the attach (and the single-attach mutex) indefinitely.DaemonSession.attachViaIosgains anattachBudget(default 90s) gating whether recovery is attempted. An attach that recovers holds the mutex (attach_in_progress) until it completes.sleuthMcpVersion0.6.0 → 0.6.1.
0.6.0 #
Snapshot projection + pagination + disk-handoff. Pins sleuth 0.35.0.
get_snapshotgains optionalsections(List- New
diskHandoff: boolarg. When true the sidecar writes the envelope to aDirectory.systemTempfile (mode 0600, crypto-random name) and returns{path, sizeBytes, sha256}plus projection metadata instead of inlinedata. Files are deleted ondetach_app, on sidecar shutdown, and swept by age (30 min) on each write. When the app predates projection support the sidecar stamps_projectionApplied: by_sidecar_fallback. compare_snapshotsrejects diffing differently-projected snapshots (arg_section_mismatch) — compared as a Set so list order of_projectedSectionsis irrelevant; also cross-checks_projectionLimits. RejectsmaxIssueCount-capped inputs entirely (arg_capped_issues_uncomparable): a truncated top-N window can't be diffed since an issue leaving the window looks identical to one that resolved.evaluateBudgetsreturnsarg_missing_required_section(instead of a generic drift error) when a projected snapshot omits a section budgets need, andarg_capped_issues_unbudgetablewhen the snapshot was projected withmaxIssueCount(truncated issue list would make budget counts wrong).maxRouteCount-only projections still budget normally.get_snapshotsurfaces app error envelopes inline even underdiskHandoff: true(never writes an error to a temp file). On the lineage-fallback path against a pre-0.35 app, the inline path returnsprojection_unsupported_by_app(the full payload would overflow the response cap) while the disk-handoff path writes + stampsby_sidecar_fallback. Emptysections: []is treated as a full-payload request, not a projection.- Disk-handoff is fail-closed: the per-pid temp dir (
0700) and file (0600) areFileStat-verified; if owner-only perms can't be established on POSIX the file is deleted anddisk_handoff_failedis returned. The payload may carryrecentRequests[].url(query tokens), so loose permissions are refused rather than tolerated. sleuthMcpVersion0.5.1 → 0.6.0;sleuthPackageVersionPin0.34.0 → 0.35.0;acceptedPriorLineages = {0.34}one-cycle fallback.- Adds
cryptodependency (sha256 for the handoff pointer). attach_appnow declaresforceRelaunchin itsinputSchema, so the documented stale-mDNS-recovery arg is reachable over MCP (was rejected asarg_unknown). An audit cross-checks documentedattach_appargs against the liveinputSchemato prevent allowlist drift.
0.5.1 #
iOS-direct attach hardening. No wire-shape change.
attach_app(udid:, bundle:, forceRelaunch:)new arg skips the Bonjour probe and drivesxcrun devicectl process launchdirectly. Auto- retries internally when the probe returns a wsUri whosebridge.connectfails withConnection refused— recovers from stale iOS mDNS without sidecar restart.bridge.connectend-to-end timeout-bounded (10s) insideDaemonSession.attachViaIos; bootstrapgetVM()bounded per-RPC at 3s. Half-open VM services that accept the WS but never return RPC no longer wedge the attach mutex; surfaces asios_vmservice_unreachable: bridge connect timed out.IosAttachExceptionthrown by the pipeline recordsstate: error+ typedlastErrorbefore rethrow, soapp_statusafter a failed attach reflects the failure instead of stayingattaching. Nextattach_appauto-recovers.withPidfileLockgainsPidfileLockGuard.registerSpawn(process, pidfile). Lock timeout after spawn but before pidfile write SIGKILLs the registered child + deletes the pidfile, preventing unreapable orphans.mapBridgeConnectErrorToLastErroradds wireless pins:Operation not permitted,Network is unreachable,Failed host lookup→ios_vmservice_unreachablewith wireless remedy text.attach_apptrims all routing args at the handler boundary so whitespace-only values don't slip into iOS-direct routing._cleanupiOS-teardown budget 3s → 6s so outer exceeds inner grace + SIGKILL + pidfile-delete; back-to-back detach/reattach no longer races on a bound port.- Schema doc adds
attach_app.args.forceRelaunchandhot_reload.errors.hot_reload_unsupported.
0.5.0 #
attach_appMCP tool gains iOS-direct routing: passingudid+bundledrives the full attach pipeline (devicectl launch → Bonjour resolve → iproxy tunnel → bridge.connect) in a single round-trip, removing the separatesleuth_mcp attach-iosCLI + manualattach_app(debugUrl:)copy-paste step. The standalone CLI remains for non-MCP workflows.- New
attach_appargs:udid,bundle,transport(auto|usb|wireless),authOverride.udidis mutually exclusive withdeviceanddebugUrl; daemon and direct-WS paths are unchanged. - New
AppStatusPayloadfields:transportMode(wired|wireless|unknown) andwsUri, populated only on iOS-direct sessions. - Typed error envelopes for the iOS path:
ios_missing_bundle,ios_ambiguous_args,ios_invalid_transport,ios_missing_tool,ios_launch_failed,ios_bonjour_timeout,ios_ambiguous_pairings,ios_no_matching_auth,ios_iproxy_failed,ios_cancelled,ios_vmservice_busy. Each carries a structureddatablock with remedy text when applicable. detach_appextended with a bounded state machine:bridge.disconnect(2s timeout) → iproxy teardown (3s timeout) → pidfile removal. Partial state always clears even if either phase hangs.hot_reloadMCP tool returns the typedhot_reload_unsupportederror on iOS-direct sessions (no flutter daemon child to invokeapp.restart); remedy is to detach + reattach viadevice:.- New
IosAttacherclass (lib/src/cli/ios_attach_pipeline.dart) encapsulates the pipeline with the same injection seams the CLI uses; callers receiveIosAttachResult+ teardown callback. The pipeline throwsIosAttachException(kind, message, data)for categorised failure mapping by callers. - Detects the device-side VM service "Connection reset post-handshake"
symptom that occurs after the first MCP attach + detach on a single
app instance and surfaces it as
ios_vmservice_busywith a remedy (swipe-kill the app on device or rebuild the profile binary).
0.4.2 #
attach-ios: bump Bonjour collect window 4s → 8s so the USB-interface authCode (which announces later than WiFi on iOS 17.5) is captured before selection.attach-ios: print all collected announcements before selection so re-running with--auth <code>is one copy-paste away when the heuristic picks wrong.tool/attach_ios.shmirrors the same selector + diagnostic output.
0.4.1 #
- New
sleuth_mcp attach-ios <udid> [--bundle <id>] [--port <n>] [--auth <code>]subcommand. Bundlesxcrun devicectl process launch --terminate-existing, Bonjour resolution viadns-sd -L, andiproxytunneling into a single command. Prints the WebSocket URI forattach_app(debugUrl:)and holds the iproxy child open until Ctrl-C tears it down. Replaces the six-step manual iOS-on-real-device flow documented in the README. - New
tool/attach_ios.shbash wrapper mirroring the same flow for users without a Dart runtime / pub-activated sidecar (CI bootstrap, ad-hoc shell). Bash 3.2+, nocoreutilsdependency (uses/usr/bin/perlfor the dns-sd timeout). The Dart subcommand remains the canonical entry; the wrapper is a no-Dart fallback + readable reference for thedevicectl → dns-sd → iproxypipeline. - Requires
libimobiledevice(brew install libimobiledevice) foriproxy. macOS-only. - Selection between USB-bridged and WiFi-bridged Bonjour pairings is
first-pairing-wins by default; pass
--auth <code>to override when the heuristic picks the WiFi pairing (the WiFi authCode is refused by the on-device WebSocket gate when reached through the USB tunnel). - No wire-shape changes —
sleuthPackageVersionPinstays at0.34.0,acceptedPriorLineagesstays at{0.33}.
0.4.0 #
Companion to sleuth v0.34.0 — tool-layer schema lock + snapshot deep shape.
sleuthPackageVersionPin0.33.0 → 0.34.0.sleuthMcpVersion0.3.0 → 0.4.0.- New
doc/mcp_tool_schema.json(structured contract) +.md(human view) ship in the pub archive — locks tool-call return shapes for the 13 MCP tools. Success-pathdata:keys and error-patherrors:codes documented per tool.connect,attach_app,detach_app,app_status,hot_reload,list_devices,compare_snapshots,check_budgets,diagnoseare first-class;get_snapshot,get_issues,get_route_health,explain_issuepassthrough the correspondingext.sleuth.*envelope with documented shims (severity_filteronget_issues,lineage_route_wrapperonget_route_health). - New
test/schema/mcp_tool_schema_audit_test.dartenforces the contract — drives FakeVmBridge through every documented error code, asserts success-path keys ⊆ documented, and tests theget_route_healthlineage shim against canonical + legacy inline shapes. Mirror-parity audit asserts root sleuth ships no parallelmcp_tool_schema.{json,md}(sidecar-only file). acceptedPriorLineagesrolled from{'0.32'}to{'0.33'}— one release cycle of fallback for 0.33.x apps mid-upgrade. Drop on next release.- Snapshot deep shapes (
recurrenceTrends,sessionSummary,routeSessions) now codified in the upstreamdoc/mcp_schema.{json,md}(mirrored atpackages/sleuth_mcp/doc/mcp_schema.{json,md}). Derived from on-device captures — see sleuth'sdoc/mcp_schema_derivation.mdfor the derivation procedure and device-context limitations. - Drift-guard regex in
test/sleuth_mcp_smoke_test.darttightened to tolerate whitespace andmultiLinematching. - After v0.4.0 ships, v0.3.0 sidecars hit
version_skew_majoron attach to a v0.34.0 app — their pin (0.33.0) is in the prior lineage. Recovery:dart pub global activate sleuth_mcp(>= 0.4.0). Local pre-publish:dart pub global activate --source path packages/sleuth_mcp.
0.3.0 #
Companion to sleuth v0.33.0 — wire-schema lock.
sleuthPackageVersionPin0.32.0 → 0.33.0.sleuthMcpVersion0.2.0 → 0.3.0.- Version-skew enforcement runs on every
bridge.connect()— both theconnecttool andattach_app(daemon-spawn anddebugUrl). Previously onlyconnectgated lineage drift;attach_appcould attach to a lineage the sidecar pin couldn't speak. _enforceVersionSkewreturns the cachedext.sleuth.diagnoseenvelope on OK / minor skew, or a refusalToolCallResult(afterbridge.disconnect()) on major skew.attach_appcallssession.detach()before returning the refusal so the daemon child tears down cleanly.acceptedPriorLineagesinversion_lineage.darttolerates sleuth 0.32.x apps for one release cycle — drift surfaces asversion_skew_minor(warning), notversion_skew_major(refusal), so users mid-upgrade aren't locked out. Drop in v0.4.0.- Baseline mutations route through one
_applyBaselinechokepoint; validator + rotation guard cover connect, reconnect, and refresh uniformly._validatedlowers before the validator runs on the refresh path so a lock-free dispatcher can't observeisConnected == truemid-validation. - Schema doc mirrored at
packages/sleuth_mcp/doc/mcp_schema.{json,md}for pub.dev consumers. - Tool-layer audit (
test/schema/) deferred to v0.4.0. Tool return shapes documented indoc/mcp_schema.md"Sidecar tool layer" as a stable best-effort contract until v0.4.0 locks them byte-for-byte. - After v0.3.0 ships, v0.2.0 sidecars hit
version_skew_majoron attach to a v0.33.0 app — their pin (0.32.0) predatesacceptedPriorLineages. Recovery:dart pub global activate sleuth_mcp(>= 0.3.0). Local pre-publish:dart pub global activate --source path packages/sleuth_mcp.
0.2.0 #
Zero-config attach-mode DX. AI agents discover and explore developer-launched Flutter apps with no manual VM service URI copy/paste.
Tools (8 → 13) #
- New lifecycle tools:
attach_app,detach_app,app_status,list_devices,hot_reload.hot_restartdeferred to v0.2.1 — Android profile-mode isolate re-registration window is not yet reliably observable from the VM service afterapp.restart. attach_appwrapsflutter attach --machine: spawns the daemon child, waits fordaemon.connected(min protocol0.6.0) +app.debugPort, connects the VM bridge to the discoveredwsUri.debugUrlescape hatch bypasses daemon discovery.- Scope: Android + iOS only.
list_devicesdefaults to mobile;attach_apprejects non-mobile devices. Mobile filter falls back totargetPlatform.startsWith('ios'|'android')whencategoryis absent (Flutter 3.41.4flutter devices --machineomitscategory). - Hot reload/restart pause dispatch → drain → daemon RPC →
bridge.refreshBaseline()(orconnect()ifwsUrirotated) → resume. list_devicescachesflutter devices --machinefor 3s.
CLI #
sleuth_mcp install [--remove]writes~/.claude.jsonmcpServers.sleuthidempotently. OS advisory lock under${XDG_CACHE_HOME:-~/.cache}/sleuth_mcp/, atomic rename via.tmp,.bakpreserved._writeAtomicresolvesconfigFilesymlinks so.tmplands on the resolved target volume — iCloud-symlinked configs no longer trigger EXDEV.
Bridge + dispatcher #
VmBridge.refreshBaseline({acceptSessionRotation})— defaultfalsethrowsSessionChangedExceptionon sessionUuid rotation. Hot-restart path opts in.VmBridge.baselineGenerationcounter — resource caches key on it.- Lifecycle tools opt out of the dispatcher's generic
_toolTimeout+ post-timeoutbridge.disconnect()viaBuiltInTool.bypassesGenericTimeout. Per-operation deadlines insideDaemonSession(attachTimeout,hotRestartTimeout) govern instead. pauseDispatch({autoResumeAfter})— caller-supplied window. Hot restart passeshotRestartTimeout + 30s._validateArgsrejects undeclared keys (arg_unknown: <key>); typos no longer silently default.
Daemon protocol layer #
- Sealed
DaemonEventhierarchy.DaemonParseriterates every frame in batched[…]lines, drops non-[…]banners + malformed JSON silently, surfaces unknown event names asUnknownDaemonEvent. _sessionGenerationcounter — stale exit-code / stderr listeners from a prior attach cannot flip a fresh session intoerror.- Hot-restart settle uses a per-restart
Completer<DaemonEvent>armed in the parser listener BEFORE theapp.restartRPC. Daemons emitapp.debugPortin the same event-loop turn as the response; lazy subscribers miss it. - Hot reload (
fullRestart: false) skips theapp.debugPort/app.startedwait — daemon never emits these events for in-place reload. _cleanup()clearsappId/deviceId/launchMode/modeso partial-attach state doesn't leak into the error-state status payload.app_status.attachedis true only forready(notrestarting).attach_appdebugUrl path reportsmode: 'unknown'.- Child reap: SIGTERM → 5s → SIGKILL. Orphan reaping of flutter daemon's subprocesses is best-effort and relies on flutter daemon's own teardown.
Server architecture #
DaemonSessionLifecycleabstract inmcp_server.dartbreaks the import cycle withDaemonSession. Bound viaMcpServer.setDaemonSession.McpServer.shutdown()callsdetach()with a 2s timeout before draining the dispatch queue.
0.1.0 #
Initial release. Companion to sleuth v0.32.0.
- MCP stdio JSON-RPC server (
bin/sleuth_mcp.dart) bridging sevenext.sleuth.*VM service extensions to AI clients. - Eight MCP tools:
connect,get_snapshot,get_issues,get_route_health,explain_issue,compare_snapshots,check_budgets,diagnose. Each ships aninputSchema. - Two MCP resources cached per
sessionUuid:sleuth://encyclopedia,sleuth://causal-graph. Generation-counter guards against in-flightread()↔invalidate()interleaving. Caches drop on re-initialize. - Separate one-shot CI gate binary
bin/sleuth_check.dartreturns exit-code on budget violations. Refuses to run when the target app's sleuth lineage (major.minor) doesn't match the binary pin. connectMCP tool returnsisError: trueand disconnects the bridge on major lineage skew. Minor skew emits awarningfield.- Hot-restart detection inline on every tool call via the envelope's
sessionUuidfield. No idle polling. - Concurrent JSON-RPC dispatch with serialized stdout writes — slow
tools no longer block fast ones.
McpServer.shutdown()plusserve()'s finally drain pending dispatches and the write chain before returning; first write failure trips cooperative shutdown. - VM bridge serializes connect / disconnect / reconnect with a
Lockso concurrent dispatches can't observe half-initialized state. Per-call retry budget + shared_reconnectInFlightfuture coalesces concurrent transport-close retries onto one reconnect. RPCError(kServerError, 'Service connection disposed')from vm_service routes through the reconnect path; only true extension-level rejections becomeVmBridgeException.SentinelExceptionsurfaces as bridge exception with isolate-expired context.- Main isolate picked by
name == 'main'(orstartsWith('main')), not blindly byisolates.first— robust against background isolates (Firebase, Workmanager,compute()). Tests can override viatargetIsolateIdOverride. - Re-connecting disposes the prior VM service handle so per-attempt WebSockets don't leak.
- SIGINT/SIGTERM trigger cooperative drain +
bridge.disconnect()in afinallyblock, notexit(0).-vlogger plumbs through toRealVmBridgeso disconnect / prior-service errors land on stderr. - Hand-rolled JSON-RPC 2.0 codec with
allowMalformed: trueUTF-8 decoding so a stray byte on stdin doesn't kill the server. initializeaccepts MCP protocol versions2024-11-05,2025-03-26,2025-06-18. Echoes the client's pin when supported.inputSchemavalidation enforcesrequired,type,enum,minLength. Unknown enum values rejected before reaching the handler. Non-objectargumentsrejected explicitly.- Tool errors return
error: <message>content only — stack traces go to stderr (with-v), never the MCP response. - Shared
versionLineagehelper used by both theconnectMCP tool and thesleuth_checkCI gate. Exported from the barrel. - Discovery is
--urionly (sleuth targets ios + android; sidecar runs on the host machine).