Skip to content

[Feature] Map durable MCP jobs to the official Tasks extension #200

Description

@morluto

Motivation

GitContribute already has durable, cancellable application jobs with SQLite state, process ownership, leases, heartbeats, bounded admission, and explicit reconciliation after process failure. MCP currently exposes those jobs through a product-specific JobReference, followed by client-driven jobs.get and jobs.cancel calls.

That contract is reliable, but an agent host may route every poll back through the model. Long-running work then produces repeated model/tool turns whose only purpose is to observe unchanged state. #199 is one concrete example: a pull-request check watcher should keep polling GitHub outside the model loop and return only when CI changes materially or reaches a terminal outcome. Indexing, acquisition, validation, and large synchronization jobs have the same protocol-level shape.

The official io.modelcontextprotocol/tasks extension is intended for CI pipelines, external job systems, batch processing, and other long-running operations. It lets a server return a durable task handle from tools/call; task-aware clients can poll tasks/get or subscribe to notifications/tasks without requiring the model to interpret every intermediate state.

The dependency is not ready in GitContribute's Go stack yet:

  • GitContribute currently pins the latest stable Go SDK, v1.6.1.
  • go-sdk#956, included in the v1.7.0 prereleases, adds typed custom JSON-RPC methods and general extension plumbing.
  • That plumbing does not implement the Tasks lifecycle or allow the standard tools/call handler to return the extension's polymorphic CreateTaskResult.
  • go-sdk#626 remains open. Its draft implementation #755 targets the superseded 2025-11-25 core Tasks design rather than the extension adopted by SEP-2663.
  • Published MCP client support for the Tasks extension is not yet established broadly enough to claim that polling will happen outside model turns.

This issue should preserve the intended architecture without authorizing a local protocol implementation against a prerelease or incomplete SDK.

Proposed Solution

Adopt the official Tasks extension only after both of these gates are met:

  1. A stable official Go SDK release supports the current Tasks extension, including task-producing tools/call, tasks/get, tasks/update, tasks/cancel, capability negotiation, notifications, and conformance coverage.
  2. At least one target MCP host used with GitContribute negotiates the extension and manages polling or subscriptions outside the model loop. Server support alone is not enough to claim lower model-token use.

Do not vendor the SDK, fork its protocol types, override the standard tools/call route, or assemble a partial Tasks implementation from custom JSON-RPC methods. If useful, contribute missing generic support upstream first.

Once the gates are met, map the existing application job model rather than creating a second executor:

GitContribute job MCP task
queued or running working
succeeded completed with the original operation result
failed failed with the typed operation error
cancelled cancelled

A durable job that successfully returns per-item partial, retryable, or unavailable outcomes remains a completed task containing that structured result; transport task failure must not erase application-level partial results.

The eventual adapter should preserve these invariants:

  • create the durable job and task identity atomically before returning a handle;
  • keep task IDs opaque and scoped to the same local authority as existing job reads;
  • make tasks/get a bounded corpus read with no hidden GitHub access, process execution, or job submission;
  • derive pollIntervalMs from useful retry/progress guidance rather than a fixed busy-poll interval;
  • treat ttlMs as result-retention policy, distinct from queue, execution, or external-operation timeouts;
  • route tasks/cancel through the existing persisted cooperative-cancellation path;
  • emit task notifications only for meaningful status/progress transitions and retain tasks/get as the authoritative recovery path;
  • map process loss or stale ownership to an explicit failed task instead of replaying external work silently;
  • preserve bounded request/result payloads, admission limits, terminal-state immutability, and exact operation side-effect annotations;
  • keep the existing JobReference plus vectorized jobs.get/jobs.cancel behavior for clients that do not negotiate the extension.

input_required and tasks/update should remain unused until an application workflow genuinely requires mid-flight user input. They should not be introduced merely to report progress.

Validation should use the official SDK and conformance fixtures rather than implementation-mirroring JSON tests. Cover stdio and streamable transport, negotiated and legacy clients, restart/reconciliation, cancellation races, TTL expiry, result retrieval, overload, cross-process ownership, notification loss, and terminal result parity with the current job API. A task-aware host evaluation should then compare complete workflow correctness, model calls, tool calls, payload, and elapsed time against the existing polling flow. #199 can serve as the first end-to-end workload after its GitHub watcher exists.

Alternatives Considered

  • Build Tasks on go-sdk#956 custom methods. This could register tasks/get, tasks/update, and tasks/cancel, but it cannot cleanly make standard tools/call return a task result. It would be a partial, hand-maintained protocol implementation that hosts may not recognize.
  • Add a custom blocking jobs.wait MCP tool. It could reduce short polling loops, but long-lived calls inherit client/proxy timeouts, are not naturally resumable, and duplicate an evolving standard.
  • Keep vectorized jobs.get polling. This remains the correct compatibility fallback and may be sufficient when a host already polls tools without involving the model.
  • Use a blocking CLI command. This is the practical near-term route for [Feature] Wait for pull-request checks without client-side polling #199 because one process can own the GitHub wait without changing MCP. It does not replace protocol-native background work for MCP clients.

Impact

The work would eventually affect MCP capability negotiation and adapters, the application job projection, task retention/notification plumbing, protocol tests, and architecture documentation. It should not introduce another scheduler, change corpus reads into implicit network operations, mutate GitHub, or execute repository-controlled code.

Until the dependency and client gates are met, this issue is a tracked architecture decision rather than an implementation task or reason to adopt an SDK prerelease.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions