Skip to content

Add MCP Tasks extension support (SEP-2663) - #1

Open
rgarcia wants to merge 14 commits into
mainfrom
hypeship/mcp-tasks-extension
Open

rgarcia wants to merge 14 commits into
mainfrom
hypeship/mcp-tasks-extension

Conversation

@rgarcia

@rgarcia rgarcia commented Sep 19, 2026

Copy link
Copy Markdown

Adds support for the MCP Tasks extension (io.modelcontextprotocol/tasks): long-running tools can return a task handle instead of blocking, and the adapter polls it to completion behind the normal tool-call contract. Elicitation during a task goes through the existing elicitation UI (answered via tasks/update), aborting a call sends a cooperative tasks/cancel, and a failed task throws the same typed error a synchronous call would have.

No configuration needed: the session only attaches when a 2026-07-28 server advertises the extension, so nothing changes for servers without task support. tasks: false opts a server out. Legacy 2025-11-25 experimental tasks are out of scope.

Built on @modelcontextprotocol/ext-tasks, the official requester package. The published SDK client rejects task wire shapes, so task traffic runs on a raw JSON-RPC channel chained onto the connected transport (mcp-tasks.ts) — the same approach as the MCP Inspector's integration. The transport object is never replaced: the stdio negotiation probe, hasPerRequestStream cancellation, and /mcp-trace behave identically. One documented trade-off: while a task session is active, tool calls skip Client.callTool's client-side output-schema validation.

20 new tests cover the channel, error mapping, and an end-to-end v2 task flow (working → completed, input_required → elicitation → tasks/update, failure, cancellation). Also verified against a real third-party server implementation, fastmcp-tasks 4.0.5 over Streamable HTTP: task-backed and immediate calls, elicitation round trip, and mid-task cancel.

nicobailon and others added 13 commits September 19, 2026 07:17
* fix: resolve server-scoped raw MCP tool names in describe

* fix: scope tool suggestions to selected server

* docs: advertise server-scoped describe support

* fix: make server-scoped discovery errors actionable

* test: cover server-scoped describe gateway

* test: cover scoped ambiguity and suggestions

* fix: preserve scoped describe context

* fix: disambiguate scoped tool names

---------

Co-authored-by: Shiloh Heurich <sheurich@fastly.com>
Co-authored-by: Shiloh Heurich <1778483+sheurich@users.noreply.github.com>
* feat(commands): add /mcp edit to open the shared config in an editor

* fix(commands): validate /mcp edit text as a JSONC object and create the config directory

* refactor(commands): trim /mcp edit and its tests

* fix(commands): check the runtime owner before opening the /mcp edit editor

* fix: preserve symlinked MCP configs

---------

Co-authored-by: turisanapo <selvaggiodavide@hotmail.it>
* Fix schema guidance on MCP domain errors

* Validate proxy arguments before dispatch

* fix: isolate proxy schema validation

---------

Co-authored-by: Schwa Aresty <schwa@testdouble.com>
* fix: support CJK lexical tool search (nicobailon#607)

* fix: tighten bounded Unicode search

* refactor: simplify Unicode search tokens

* refactor: trim CJK search implementation

* test: focus CJK search coverage

* fix: preserve bounded Unicode search coverage

* fix: isolate Unicode search from ASCII stemming
* fix: resolve self-namespaced MCP tools

* fix: scope cold candidate resolution

* refactor: centralize MCP name ownership

* refactor: simplify MCP name resolution

* test: cover name ownership publicly

* fix: prioritize exact upstream tool names

* fix: align MCP tool describe ownership

* fix: resolve global describe precedence
* fix: refresh bearerToken !command shellout per HTTP request

For lazy-keep-alive HTTP servers, the StreamableHTTPClientTransport
reuses the resolved bearer token across every request. With
`bearerToken: "!command"`, that token is resolved once at connect
time and frozen in the transport's requestInit until reconnect. A 24h
Cloudflare Access JWT rotates mid-session, the next request returns
401, and `shouldReconnectAfterRefresh` ignores 401, so the lifecycle
manager logs the failure and never reconnects. Quitting pi is the only
way out.

Route `!command` bearer tokens through a per-request FetchLike backed
by a TTL-cached resolver (default 5 minutes, override with
PI_MCP_ADAPTER_BEARER_COMMAND_TTL_MS) so the shellout runs at most
once per window per server. A transient shell failure falls back to
the last good token instead of auth-failing every subsequent request.

Also extend `shouldReconnectAfterRefresh` to treat 401 as a reconnect
trigger. Static, env-var, and stored bearer tokens still bake into
requestInit at connect time; without this hook they were permanently
stuck on the first rotation until pi restart.

* fix: harden bearer command refresh

Follow up on @kesor's bearer refresh contribution with async cancellation, retry throttling, package coverage, and pinned-SDK 401 handling.

* fix: isolate bearer refresh cancellation

Keep request cancellation out of helper failure backoff, await Windows process-tree cleanup, and scope 401 reconnects to bearer-authenticated servers.

* fix: preserve bearer request semantics

Make the merged Request the sole header-composition owner, remove unused cache invalidation surface, and reject ambiguous TTL values.

* refactor: trim bearer refresh follow-up

Remove redundant state, duplicated coverage, and stale explanatory prose while retaining @kesor's behavior and explicit changelog credit.

* refactor: simplify bearer refresh state

Use explicit cache deadlines, remove placeholder in-flight promises, and share portable Node command fixtures without changing the @kesor contribution contract.

---------

Co-authored-by: kesor <kesor@users.noreply.github.com>
* feat: add secure TypeSafe credential foundation

* feat: add host-mediated Jev script evaluation

* feat: add opt-in semantic tool search

* docs: complete Jev integration guidance and examples

* fix: harden Jev validation and search lifecycle

* fix: tighten Jev credential and source invariants

* refactor: tighten Jev guidance and examples

* refactor: minimize Jev search internals

* fix: enforce Jev token and abstention budgets

* build: refresh public artifacts after promotion

* fix: enforce script Jev provenance and shared budgets
…663)

Long-running tools on 2026-07-28 connections can return a task handle
instead of blocking; the adapter polls the task to completion and
returns the final result, keeping the normal tool-call contract.
Task-time elicitation routes through the existing elicitation UI and is
answered via tasks/update, aborting a call sends a cooperative
tasks/cancel, and failed tasks surface as the typed JSON-RPC error a
synchronous call would have produced.

Built on @modelcontextprotocol/ext-tasks. The published SDK client
rejects task wire shapes, so the requester session runs on a raw
JSON-RPC channel (mcp-tasks.ts) chained onto the connected transport:
string-prefixed request ids that cannot collide with the SDK's numeric
ids, responses consumed before the SDK sees them, Mcp-Name routing
headers forwarded per the spec's Streamable HTTP binding. The transport
object itself is never replaced, so class identity, the stdio
negotiation probe, hasPerRequestStream cancellation, and /mcp-trace all
behave identically.

No configuration needed: the session attaches only when a modern-era
server advertises the extension, and tasks: false opts a server out.
Legacy 2025-11-25 experimental tasks are out of scope.

Verified end-to-end against fastmcp-tasks 4.0.5, a third-party server
implementation: task-backed and immediate calls, input_required
elicitation round trip, and mid-task cancellation.
@rgarcia
rgarcia force-pushed the hypeship/mcp-tasks-extension branch from ccb3b99 to 24b018b Compare September 20, 2026 14:20
@rgarcia rgarcia changed the title Add opt-in MCP Tasks extension support (io.modelcontextprotocol/tasks, SEP-2663) Add MCP Tasks extension support (SEP-2663) Sep 20, 2026
… to sampling

An abort that lands while session.callTool is resolving fired no
listener, so the local call ended while the remote task kept running;
fire the cancel hook directly for an already-aborted signal. Task-time
sampling now merges the per-input task signal into the handler's
getSignal so cancelling a task also stops the local sampling run.
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.

2 participants