Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/guide/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,17 @@ ggcode handles these requests by routing them through the same `ask_user` intera

No configuration is needed — elicitation is enabled automatically when an interactive session is active.

## MCP Tasks (Asynchronous Tool Execution, SEP-1686)

Since protocol revision 2025-11-25, MCP servers may execute tool calls asynchronously. When ggcode sends a `tools/call` with the `task` request option, a compliant server can answer immediately with a *task descriptor* (`resultType: "task"`) instead of blocking until the tool finishes.

ggcode's client implements the full task protocol:
- `CallToolAsTask` sends the task-augmented call, then polls `tasks/get` until the task reaches a terminal state, and fetches the final tool result via `tasks/result`. The server's `pollInterval` hint is honored, clamped to [200ms, 10s], and the total polling budget is capped at 10 minutes so a stuck server cannot hang a call forever.
- `ListTasks`, `GetTask`, `CancelTask`, and `HasTasks` expose task management; listing is gated on the server's advertised `tasks` capability (uncapable servers get an empty list, never an error).
- During initialize, ggcode declares client `tasks` support in both the legacy handshake and the modern per-request `_meta` envelope.

A task that ends `failed` or `cancelled` surfaces as an error carrying the server's `statusMessage`. Tasks that pause in `input_required` are reported to the caller rather than auto-resolved, since the interactive flow requires user-driven input.

## Subscription Streams (MCP 2026-07-28)

Protocol revision 2026-07-28 added correlated notification streams: a client may open a subscription with the `subscriptions/listen` request, and every notification the server sends on that stream carries a `_meta` field binding it to the subscription. This closes a long-standing ambiguity — when an agent talks to several MCP servers concurrently, a bare `notifications/tools/list_changed` cannot be attributed to a specific connection with certainty.
Expand Down
15 changes: 14 additions & 1 deletion internal/mcp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ type Client struct {
modernVersion string
modernServerInfo *Implementation
legacyServerInfo Implementation
// taskPollOverride replaces the SEP-1686 poll sleep in tests only;
// always zero in production.
taskPollOverride time.Duration
mu sync.Mutex
stderrMu sync.RWMutex
stderrBuf strings.Builder
Expand Down Expand Up @@ -2999,6 +3002,9 @@ type ClientCaps struct {
} `json:"roots,omitempty"`
Sampling *SamplingCapability `json:"sampling,omitempty"`
Elicitation *ElicitationCapability `json:"elicitation,omitempty"`
// Tasks declares SEP-1686 task protocol support: the client implements
// tasks/get, tasks/list, tasks/cancel and tasks/result handling.
Tasks *struct{} `json:"tasks,omitempty"`
}

// ElicitationCapability is the initialize capability object for elicitation
Expand Down Expand Up @@ -3039,7 +3045,10 @@ type ServerCaps struct {
Tools *ToolsCapability `json:"tools,omitempty"`
Resources *ResourcesCapability `json:"resources,omitempty"`
Prompts *PromptsCapability `json:"prompts,omitempty"`
Logging *struct{} `json:"logging,omitempty"`
// Tasks is the SEP-1686 server capability; presence gates task-augmented
// tool calls and the task management methods.
Tasks *TasksCapability `json:"tasks,omitempty"`
Logging *struct{} `json:"logging,omitempty"`
// Completions mirrors the MCP completion capability key. Per spec
// (2025-06-18, "Completion": Capabilities) servers that support argument
// autocompletion declare `{"capabilities": {"completions": {}}}` — the
Expand Down Expand Up @@ -3198,6 +3207,10 @@ type CallToolParams struct {
// MRTR retry fields; see GetPromptParams.
InputResponses map[string]json.RawMessage `json:"inputResponses,omitempty"`
RequestState string `json:"requestState,omitempty"`
// Task is the SEP-1686 task request option: when set, the server may
// answer with a task descriptor (resultType "task") instead of the
// tool result; see CallToolAsTask.
Task TaskRequestOptions `json:"task,omitempty"`
}

type CallToolResult struct {
Expand Down
5 changes: 5 additions & 0 deletions internal/mcp/discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,11 @@ func (c *Client) clientCapsLocked() ClientCaps {
// consent-gated out-of-band handoff (it never auto-opens URLs).
caps.Elicitation = &ElicitationCapability{Form: &struct{}{}, URL: &struct{}{}}
}
// SEP-1686: the client always implements the task management methods
// (tasks/get, tasks/list, tasks/cancel, tasks/result), so declare the
// tasks capability unconditionally - it flows through both the legacy
// initialize params and the modern per-request _meta envelope.
caps.Tasks = &struct{}{}
return caps
}

Expand Down
7 changes: 7 additions & 0 deletions internal/mcp/mrtr.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,13 @@ func (c *Client) mrtrLoop(ctx context.Context, method string, params mrtrRetryPa
switch env.ResultType {
case "", ResultTypeComplete:
return json.Unmarshal(raw, out)
case ResultTypeTask:
// SEP-1686 task descriptor: pass the raw envelope through so the
// caller (CallToolAsTask) can run the poll/result protocol. Only
// task-augmented requests can legitimately receive this shape;
// non-task callers decode it into their result type and surface
// the mismatch themselves.
return json.Unmarshal(raw, out)
case ResultTypeInputRequired:
if round >= maxMRTRRoundTrips {
return fmt.Errorf("mcp[%s]: %s exceeded %d input_required round trips (MRTR loop guard)", c.name, method, maxMRTRRoundTrips)
Expand Down
Loading
Loading