Skip to content

feat: MCP 2026-07-28 alongside 2025-11-25, SEP-2663 tasks, rmcp 3.6.0 (0.9.0) - #19

Merged
WeekendSuperhero merged 25 commits into
mainfrom
mcp-2026-tasks-v2
Oct 22, 2026
Merged

WeekendSuperhero merged 25 commits into
mainfrom
mcp-2026-tasks-v2

Conversation

@WeekendSuperhero

@WeekendSuperhero WeekendSuperhero commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Retires SEP-1686 v1 tasks, which were measured unused (0.8.0). Moves to rmcp 3.x with the protocol pinned to 2025-11-25.
  • Adds MCP 2026-07-28 alongside 2025-11-25, with the SEP-2663 tasks extension (0.9.0). A slow call from a client that declared the extension becomes a task, on a token from the connection's lifetime.
  • Ends on =3.7.0, exact-pinned to the Agent Muse workspace's rmcp, so the app links one copy.
  • Rebased onto this branch (4ef0cd9, the commit Agent Muse beta points at). Upstream changed only Cargo.lock here, so each commit's lockfile is regenerated from its parent's, with rmcp-macros held at rmcp's version.

Changes

  • 8bef363 feat!: retire MCP tasks (SEP-1686); 0.8.0. The 22 execution(task_support) attributes, the five task handlers, tasks.rs and the task capability go.
  • 5314afc build: rmcp 3.5.1. The protocol stays MCP 2025-11-25.
  • be381a5 feat: MCP 2026-07-28 alongside 2025-11-25, with SEP-2663 tasks; 0.9.0.
    • supported_protocol_versions is bounded by MAX_PROTOCOL_VERSION.
    • initialize answers 2025-11-25, and server/discover answers only at 2026-07-28.
    • Tasks are promoted late.
    • The fresh_logins/idle_reuses counters are labelled pool-wide.
  • 6d8e44c fix: server/discover lists every supported revision, as a -32022 does.
  • 0ea3c21 build: rmcp 3.6.0. No API this crate uses changed. 3.6.0 adds a 16 MiB stdio message cap, stricter JSON-RPC parsing, and refusal of server-to-client requests on 2026 HTTP sessions.

Test plan

  • ./ci-check.sh (fmt, clippy -D warnings on all targets and features, build, docs, nextest) on 3.5.1 and again on 3.6.0: 491 passed, 2 skipped.
  • cargo metadata --locked holds at every commit. The 3.6.0 lockfile change is rmcp, rmcp-macros and rmcp's new libc edge, and nothing else.

Pairs with the Agent Muse superproject branch weekendsuperhero/rmcp_3_5_0_upgrade, whose pointer commits reference these SHAs.

WeekendSuperhero and others added 22 commits September 22, 2026 04:44
…ged `OK`. A new `release_after_mutation` replaces it at all six post-mutation sites. It returns the session to the pool only if the connection answers, and never fails the call, because the mutation's own reply is the result. A server dropping the client right after an EXPUNGE no longer turns a finished delete into an error.

  - **Correction to my own finding:** `update_flags` never confirmed its STOREs, so there was no "completed flag change" to misreport. Its real bugs were in reading the result back. A dropped connection came out as "message not found", and it could return another message's flags when another client's update arrived first. Both are fixed.
- **#18, short LIST:** a NOOP check now follows LIST, so a list cut short by a dropped connection is never returned or cached.
- **#19, reads trusting a dead connection:** this was wider than reported. Besides misleading, "message not found" from a dropped connection made the subscription move, unsubscribe and ranking samples prune the ranking cache. The cache forgot messages that still existed, breaking the samples' own documented rule that "an outage must not masquerade as deletion".
  - **The fix:** `with_session_retry` now probes after the read, so `search_messages`, `get_messages`, `list_identities` and the other wrapped reads retry once on a fresh connection instead of returning a partial answer. A message now counts as missing only when a live server leaves it out.

I added nine tests; eight failed before the fix for the reason they target, and one is a control. The existing sample-subject test had to answer the new NOOP. The cost is one extra NOOP round trip per retried read, and per fetch that comes back missing a message.
Agent Muse links this crate and is consolidating on one rustls crypto
provider, aws-lc-rs: ring has no post-quantum key exchange, and two
providers mean rustls can't pick one on its own. mail-auth's `aws-lc-rs`
feature also selects hickory's TLS and DNSSEC backends, so ring leaves
the build entirely. Lockfile: untrusted 0.7.1 added (aws-lc-rs), nothing
else moved.
native-tls meant three TLS stacks for one connection: Secure Transport on
macOS (deprecated since 10.15, and it never offered TLS 1.3, so IMAP
topped out at 1.2), SChannel on Windows, OpenSSL on Linux, where it also
needed libssl-dev to build. Agent Muse links this crate and ships one
rustls provider, aws-lc-rs.

IMAP now connects through tokio-rustls on aws-lc-rs (its `rustls`
re-export is the one used, so the two can't drift), and
rustls-platform-verifier hands the certificate check to the OS:
Security.framework, CryptoAPI, or the system roots on Linux. Roots a
person or their MDM installed still count. The provider is named, not
taken from the process default, so agentmail behaves the same whether
or not its host installed one. The configuration is built once per
process (tls_client_config, now public so examples/probe_xoauth2.rs
probes the same connection).

What a server loses: TLS 1.2 suites without forward secrecy or in CBC
mode; native-tls already refused anything below 1.2. What a host loses:
AgentmailError::Tls now carries the handshake's std::io::Error (tokio-
rustls's report, rustls's error inside) and is no longer a From
conversion, which is breaking. It still counts as a connection error,
so a failed handshake is retried once on a fresh connection as before.

Tests: the configuration builds with the platform verifier, and an
ignored live test completes a verified handshake with imap.gmail.com:993
and reads the IMAP greeting. Run on macOS, it negotiated TLS 1.3.

rust-version moves to 1.99, the toolchain Agent Muse builds with.

Co-Authored-By: Claude <noreply@anthropic.com>
dbus-secret-service-keyring-store reached Secret Service through the C
libdbus (libdbus-sys), the last system library agentmail linked on
Linux after the move to rustls: every Linux build needed libdbus-1-dev
and pkg-config, in this repo's CI, release, security and nightly-stress
workflows and in Agent Muse's Linux CI leg.

zbus-secret-service-keyring-store is the same store over zbus, a D-Bus
implementation in Rust: the same Store::new() -> Result<Arc<Store>>, and
identical code for the item's search attributes (service, username,
target), so a password saved by the old store is found by the new one.
Features rt-async-io-crypto-rust: zbus runs its own async-io executor,
so the store's blocking calls work from any thread, a tokio worker
included, rather than needing a tokio context (rt-tokio); crypto stays
pure Rust. On Linux the graph now has no libdbus-sys and no openssl-sys;
what -sys crates remain build from bundled sources (aws-lc-sys, whose
build script only optionally asks pkg-config, and libsqlite3-sys).

The workflows stop installing libdbus-1-dev and pkg-config: the
reusable CI and release workflows install their *-linux-packages only
when given, and security and nightly-stress drop their apt step.

Checked on macOS: fmt, clippy (all targets, -D warnings) and the tests.
The Linux arm of main.rs is not compiled here (no Linux C toolchain on
this machine); its one change is the store's crate name, whose
constructor and trait impl match.

Co-Authored-By: Claude <noreply@anthropic.com>
aws-lc-sys before 0.46 emits bindgen's `#[link_name = "\u{1}aws_lc_..."]`.
The `\u{1}` is an LLVM escape that cranelift takes literally, so a cranelift
build fails to link with doubled-underscore symbols
(`__aws_lc_0_45_0_SHA256`). aws/aws-lc-rs#1231, fixed by #1234 in 0.46.0.

Agent Muse's own lockfile is already on these versions. This one matters when
the agentmail binary is built by itself, as app-api's reload tests do under the
host repo's NIGHTLY_CRANELIFT=1. Lock-only: no other package moved, and the
suite passes on LLVM (494 tests).
The server no longer advertises capabilities.tasks, no tool declares
execution.taskSupport (22 did), and tasks/list|get|result|cancel are gone,
with the per-account serialization of destructive tasks, the
notifications/tasks/status push and the related-task _meta. Agent Muse, which
embeds this server, measured the task surface unused, and every rmcp 3.x
release removes the MCP 2025-11-25 task model. A task-augmented tools/call is
refused and the tool never runs; long operations still report progress and
honour notifications/cancelled.

Also: the fresh_logins / idle_reuses counters are labelled pool-wide
(pool_fresh_logins / pool_idle_reuses in the log lines; ConnectionStats
documents that they cover every account and reset with the pool).

Breaking for a 0.x crate, hence 0.8.0.
rmcp =2.2.0 -> =3.5.1, exact-pinned to the embedding Agent Muse workspace's
copy so the app links one rmcp.

The revision does not move with the SDK (rmcp 3.5.0 made LATEST and Default
2026-07-28): get_info names PROTOCOL_VERSION (2025-11-25), and
supported_protocol_versions() = known_up_to(PROTOCOL_VERSION) bounds
initialize, server/discover and per-request _meta. A 2026-07-28 probe is
refused -32022 with that list, which a probing client reads as legacy; discover
itself answers method-not-found, so a probe at a 2025 version cannot start the
2026 stateless lifecycle at 2025-11-25.

Mechanical: ServerInfo -> ServerConfig; the Meta extractor ->
RequestMetaObject; schema_for_output returns the schema (no Result; the
object-root guard it carried is tools_list_has_36_annotated_tools); the
read_resource response enum; ListToolsResult's new fields defaulted.

A task-augmented tools/call now runs as a plain call (rmcp 3.x has no `task`
param), the 2025-11-25 rule for a server without the capability; the test
flips from a_task_augmented_call_is_refused_never_run.

New tests: a_2026_07_28_client_is_negotiated_down_to_2025_11_25,
server_discover_lists_only_versions_up_to_2025_11_25.
Both lifecycles from one server. supported_protocol_versions() is
known_up_to(MAX_PROTOCOL_VERSION), named as 2026-07-28 in the source, never
rmcp's LATEST. initialize answers LATEST_WITH_INITIALIZE (2025-11-25), also
to a client asking for 2026-07-28. server/discover is answered only at a
revision without a handshake and lists only those (2026-07-28); at a 2025
version it is method-not-found, so no client runs the stateless lifecycle at
a revision that defines none. A 2025 client sees what 0.8.0 sent, plus the
tasks extension.

Tasks return as the io.modelcontextprotocol/tasks extension, offered on both
revisions and promoted late (src/mcp/tasks.rs). A call becomes a task only
when:
- the client declared the extension,
- the tool is one of the 22 long-running ones (0.8.0's task list),
- it is still running after 2 seconds,
- and admission allows it (32 running, 1,024 held).
Everything else is answered inline exactly as before. Each task lives 4 hours
and suggests 1-second polls.

- The operation runs on a child of the connection's lifetime token, never
  the request's: rmcp cancels a request's token as it sends the response, and
  a task's response is its handle (the SEP-1686 self-cancel bug).
- tasks/cancel is cooperative. A task whose tool saw it through its CancelFn
  is cancelled; one that finished first is completed. A panic fails the task
  instead of leaving it working until the TTL.
- After promotion, progress is the task's status message plus
  _meta["io.agentmuse/progress"] on tasks/get.
- Closing the connection cancels every operation, waits 5 seconds, then
  aborts what is left.

Library: the ten mutations that take a CancelFn leave the per-account queue
when it fires (lock_account_mutation_unless_cancelled), instead of when the
run ahead of them ends.

Tests: the runtime (inline under budget, outliving the request token, cancel
before and after promotion, finishing despite a cancel, panic before and
after, TTL abort, progress, admission, shutdown), a promoted task polled to
its result over the JSON-RPC wire, discover at 2026 and 2025, an unknown
revision, tasks/* without the extension, and an unknown task id.

Docs: MCP.md (two lifecycles; Tasks), README, DESIGN, DECISIONS (two 0.9.0
entries), CHANGELOG.
discover listed only the handshake-free revisions (2026-07-28), to keep a
stateless client off 2025. But the version list a -32022 gives and the one
discover gives are one fact, and the official conformance suite holds them to
it: sep-2575-server-unsupported-version-error requires every version in a
-32022 to appear in discover's supportedVersions. The gate on discover itself
(method-not-found at a 2025 version) is what keeps a stateless client off
2025, and it stays.

Test, MCP.md, CHANGELOG and the 0.9.0 decision follow.
rmcp =3.5.1 -> =3.6.0, exact-pinned to the embedding Agent Muse
workspace's copy so the app links one rmcp. No API this crate uses
changed (ServerHandler, the macros and the transports are identical),
and the protocol constants are unchanged.

What 3.6.0 changes here: a single incoming stdio message over 16 MiB is
dropped (async_rw), a JSON-RPC message carrying both result and error,
or method with an unreadable id, is rejected, and server-to-client
requests on a 2026-07-28 HTTP session are refused at the source.
TaskRuntime::shutdown asked every operation to stop, gave them five
seconds, then had rmcp drop the tasks, and dropping an operation aborted
it wherever it was. One chunk of a bulk move on a slow server can take
longer than that, so a closing connection (a mail-account edit, a reload)
could cut a move between its COPY and its EXPUNGE.

Dropped while the connection shuts down, an operation is now detached: its
token is already cancelled, so it finishes the chunk it is in and stops at
its next check. Any other drop (rmcp's TTL, the one hard stop) cancels the
operation's token before aborting it, so work an abort cannot reach stops
too.
@WeekendSuperhero
WeekendSuperhero changed the base branch from agentmail_use_roots to main October 22, 2026 21:35
Comment thread tests/mcp_integration.rs Dismissed
@WeekendSuperhero
WeekendSuperhero merged commit e18bb40 into main Oct 22, 2026
11 checks passed
@WeekendSuperhero
WeekendSuperhero deleted the mcp-2026-tasks-v2 branch October 22, 2026 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants