From 449a0cd3d7240e6a574d38186b5c6012f26a3381 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Fri, 14 Aug 2026 21:32:42 +0200 Subject: [PATCH 1/2] mcp: add tasks extension capability negotiation Add ExtensionTasks, the identifier of the io.modelcontextprotocol/tasks extension, and nil-safe HasExtension accessors on ClientCapabilities and ServerCapabilities, so both peers can negotiate the extension defined by SEP-2663. AddExtension already covered declaring an extension; there was no supported way to test for one. Task execution is not implemented, and neither NewClient nor NewServer declares the extension by default: declaring it obliges a client to handle a task handle in place of any result, and a server to serve tasks/get. Reserve the "task" resultType discriminator and reject it while decoding. Previously a CreateTaskResult decoded into an empty CallToolResult with no error, so a tool call against a task-returning server silently appeared to succeed and return nothing. It now fails with UnsupportedTaskResultError, which carries the task ID. Tasks moved out of the core protocol into an extension in 2026-07-28; the earlier in-core design (SEP-1686, 2025-11-25) is not implemented. For #626 --- docs/client.md | 40 ++++++ docs/server.md | 30 ++++ internal/docs/client.src.md | 40 ++++++ internal/docs/server.src.md | 30 ++++ mcp/protocol.go | 37 +++++ mcp/tasks.go | 28 ++++ mcp/tasks_test.go | 273 ++++++++++++++++++++++++++++++++++++ 7 files changed, 478 insertions(+) create mode 100644 mcp/tasks.go create mode 100644 mcp/tasks_test.go diff --git a/docs/client.md b/docs/client.md index fe9a9637..fb5a3525 100644 --- a/docs/client.md +++ b/docs/client.md @@ -334,3 +334,43 @@ that optional capabilities outside the core protocol can be declared on the wire. Keys are namespaced as `"{vendor-prefix}/{extension-name}"`; values are per-extension settings objects. +Use `AddExtension` to declare one and `HasExtension` to test for one: + +```go +caps := &mcp.ClientCapabilities{} +caps.AddExtension("io.example/my-extension", nil) +client := mcp.NewClient(impl, &mcp.ClientOptions{Capabilities: caps}) + +cs, err := client.Connect(ctx, transport, nil) +... +if cs.InitializeResult().Capabilities.HasExtension("io.example/my-extension") { + // The server declared it too. +} +``` + +#### Tasks + +[SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663) +moved tasks out of the core protocol and into the +[tasks extension](https://github.com/modelcontextprotocol/ext-tasks), +identified by the `mcp.ExtensionTasks` constant. A server that has negotiated +it may answer a request with a durable task handle instead of the result that +was asked for, which the client then polls to completion. + +**The SDK does not implement task execution**, and does not declare the +extension by default. Declaring it is a promise to the peer: a client that +declares it must be prepared for any eligible request to return a task handle +instead of a result. Only declare it if you implement that polling flow +yourself. + +If a server returns a task handle anyway, decoding fails with +`*mcp.UnsupportedTaskResultError`, which carries the task ID: + +```go +res, err := cs.CallTool(ctx, params) +var terr *mcp.UnsupportedTaskResultError +if errors.As(err, &terr) { + log.Printf("server created task %s, which this SDK cannot resolve", terr.TaskID) +} +``` + diff --git a/docs/server.md b/docs/server.md index 9f0f8706..7dfe9248 100644 --- a/docs/server.md +++ b/docs/server.md @@ -787,6 +787,36 @@ capabilities outside the core protocol can be declared on the wire. Keys are namespaced as `"{vendor-prefix}/{extension-name}"`; values are per-extension settings objects. +Use `AddExtension` to declare one, and `HasExtension` to test what the client +declared. Client capabilities are read from the request, since as of protocol +version 2026-07-28 they travel in each request's `_meta` rather than in the +initialize handshake: + +```go +caps := &mcp.ServerCapabilities{} +caps.AddExtension("io.example/my-extension", nil) +server := mcp.NewServer(impl, &mcp.ServerOptions{Capabilities: caps}) + +// Inside a tool handler: +if req.ClientCapabilities().HasExtension("io.example/my-extension") { + // The client declared it too. +} +``` + +#### Tasks + +[SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663) +moved tasks out of the core protocol and into the +[tasks extension](https://github.com/modelcontextprotocol/ext-tasks), +identified by the `mcp.ExtensionTasks` constant. A server that has negotiated +it may answer a request with a durable task handle instead of the result that +was asked for, which the client then polls to completion. + +**The SDK does not implement task execution**, and does not declare the +extension by default. Declaring it is a promise to the peer: a server that +declares it must serve `tasks/get`, `tasks/update` and `tasks/cancel`. Only +declare it if you implement those yourself. + ### Pagination Server-side feature lists may be diff --git a/internal/docs/client.src.md b/internal/docs/client.src.md index 5c4b8f9e..234e3f47 100644 --- a/internal/docs/client.src.md +++ b/internal/docs/client.src.md @@ -183,3 +183,43 @@ that optional capabilities outside the core protocol can be declared on the wire. Keys are namespaced as `"{vendor-prefix}/{extension-name}"`; values are per-extension settings objects. +Use `AddExtension` to declare one and `HasExtension` to test for one: + +```go +caps := &mcp.ClientCapabilities{} +caps.AddExtension("io.example/my-extension", nil) +client := mcp.NewClient(impl, &mcp.ClientOptions{Capabilities: caps}) + +cs, err := client.Connect(ctx, transport, nil) +... +if cs.InitializeResult().Capabilities.HasExtension("io.example/my-extension") { + // The server declared it too. +} +``` + +#### Tasks + +[SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663) +moved tasks out of the core protocol and into the +[tasks extension](https://github.com/modelcontextprotocol/ext-tasks), +identified by the `mcp.ExtensionTasks` constant. A server that has negotiated +it may answer a request with a durable task handle instead of the result that +was asked for, which the client then polls to completion. + +**The SDK does not implement task execution**, and does not declare the +extension by default. Declaring it is a promise to the peer: a client that +declares it must be prepared for any eligible request to return a task handle +instead of a result. Only declare it if you implement that polling flow +yourself. + +If a server returns a task handle anyway, decoding fails with +`*mcp.UnsupportedTaskResultError`, which carries the task ID: + +```go +res, err := cs.CallTool(ctx, params) +var terr *mcp.UnsupportedTaskResultError +if errors.As(err, &terr) { + log.Printf("server created task %s, which this SDK cannot resolve", terr.TaskID) +} +``` + diff --git a/internal/docs/server.src.md b/internal/docs/server.src.md index 756bba3b..ee8ea216 100644 --- a/internal/docs/server.src.md +++ b/internal/docs/server.src.md @@ -405,6 +405,36 @@ capabilities outside the core protocol can be declared on the wire. Keys are namespaced as `"{vendor-prefix}/{extension-name}"`; values are per-extension settings objects. +Use `AddExtension` to declare one, and `HasExtension` to test what the client +declared. Client capabilities are read from the request, since as of protocol +version 2026-07-28 they travel in each request's `_meta` rather than in the +initialize handshake: + +```go +caps := &mcp.ServerCapabilities{} +caps.AddExtension("io.example/my-extension", nil) +server := mcp.NewServer(impl, &mcp.ServerOptions{Capabilities: caps}) + +// Inside a tool handler: +if req.ClientCapabilities().HasExtension("io.example/my-extension") { + // The client declared it too. +} +``` + +#### Tasks + +[SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663) +moved tasks out of the core protocol and into the +[tasks extension](https://github.com/modelcontextprotocol/ext-tasks), +identified by the `mcp.ExtensionTasks` constant. A server that has negotiated +it may answer a request with a durable task handle instead of the result that +was asked for, which the client then polls to completion. + +**The SDK does not implement task execution**, and does not declare the +extension by default. Declaring it is a promise to the peer: a server that +declares it must serve `tasks/get`, `tasks/update` and `tasks/cancel`. Only +declare it if you implement those yourself. + ### Pagination Server-side feature lists may be diff --git a/mcp/protocol.go b/mcp/protocol.go index 47cb9cbd..0102ebf2 100644 --- a/mcp/protocol.go +++ b/mcp/protocol.go @@ -26,6 +26,11 @@ const ( // input before it can complete the request. The client should fulfill the // InputRequests and retry the call with the responses. resultTypeInputRequired resultType = "input_required" + + // resultTypeTask is reserved by the io.modelcontextprotocol/tasks + // extension to discriminate a CreateTaskResult from a standard result. + // See [ExtensionTasks]. + resultTypeTask resultType = "task" ) type completeResultWithType struct { @@ -403,10 +408,14 @@ func (x *CallToolResult) UnmarshalJSON(data []byte) error { res Content []*wireContent `json:"content"` ResultType resultType `json:"resultType"` + TaskID string `json:"taskId"` } if err := internaljson.Unmarshal(data, &wire); err != nil { return err } + if wire.ResultType == resultTypeTask { + return &UnsupportedTaskResultError{TaskID: wire.TaskID} + } var err error if wire.res.Content, err = contentsFromWire(wire.Content, nil); err != nil { return err @@ -518,6 +527,16 @@ func (c *ClientCapabilities) AddExtension(name string, settings map[string]any) c.Extensions[name] = settings } +// HasExtension reports whether c declares the extension with the given name. +// It is safe to call on a nil *ClientCapabilities. +func (c *ClientCapabilities) HasExtension(name string) bool { + if c == nil { + return false + } + _, ok := c.Extensions[name] + return ok +} + // clone returns a copy of the ClientCapabilities. // Values in the Extensions and Experimental maps are shallow-copied. func (c *ClientCapabilities) clone() *ClientCapabilities { @@ -1033,10 +1052,14 @@ func (x *GetPromptResult) UnmarshalJSON(data []byte) error { var wire struct { res ResultType resultType `json:"resultType"` + TaskID string `json:"taskId"` } if err := internaljson.Unmarshal(data, &wire); err != nil { return err } + if wire.ResultType == resultTypeTask { + return &UnsupportedTaskResultError{TaskID: wire.TaskID} + } wire.res.resultType = wire.ResultType *x = GetPromptResult(wire.res) return nil @@ -1650,10 +1673,14 @@ func (x *ReadResourceResult) UnmarshalJSON(data []byte) error { var wire struct { res ResultType resultType `json:"resultType"` + TaskID string `json:"taskId"` } if err := internaljson.Unmarshal(data, &wire); err != nil { return err } + if wire.ResultType == resultTypeTask { + return &UnsupportedTaskResultError{TaskID: wire.TaskID} + } wire.res.resultType = wire.ResultType *x = ReadResourceResult(wire.res) return nil @@ -2307,6 +2334,16 @@ func (c *ServerCapabilities) AddExtension(name string, settings map[string]any) c.Extensions[name] = settings } +// HasExtension reports whether c declares the extension with the given name. +// It is safe to call on a nil *ServerCapabilities. +func (c *ServerCapabilities) HasExtension(name string) bool { + if c == nil { + return false + } + _, ok := c.Extensions[name] + return ok +} + // clone returns a copy of the ServerCapabilities. // Values in the Extensions and Experimental maps are shallow-copied. func (c *ServerCapabilities) clone() *ServerCapabilities { diff --git a/mcp/tasks.go b/mcp/tasks.go new file mode 100644 index 00000000..3459e9d9 --- /dev/null +++ b/mcp/tasks.go @@ -0,0 +1,28 @@ +// Copyright 2025 The Go MCP SDK Authors. All rights reserved. +// Use of this source code is governed by the license +// that can be found in the LICENSE file. + +package mcp + +import "fmt" + +// ExtensionTasks identifies the MCP Tasks extension, which lets a server answer +// a request with a durable task handle instead of the request's normal result. +// +// This SDK does not implement task execution, and does not declare the +// extension by default: declaring it obliges a client to poll a task handle to +// completion, and a server to serve the tasks/* methods. +// +// See https://github.com/modelcontextprotocol/ext-tasks. +const ExtensionTasks = "io.modelcontextprotocol/tasks" + +// UnsupportedTaskResultError reports that a peer answered a request with a task +// handle from the [ExtensionTasks] extension, which this SDK cannot resolve. +type UnsupportedTaskResultError struct { + // TaskID identifies the created task, for manual polling or cancellation. + TaskID string +} + +func (e *UnsupportedTaskResultError) Error() string { + return fmt.Sprintf("peer created task %q: the %s extension is not implemented", e.TaskID, ExtensionTasks) +} diff --git a/mcp/tasks_test.go b/mcp/tasks_test.go new file mode 100644 index 00000000..40199ecb --- /dev/null +++ b/mcp/tasks_test.go @@ -0,0 +1,273 @@ +// Copyright 2025 The Go MCP SDK Authors. All rights reserved. +// Use of this source code is governed by the license +// that can be found in the LICENSE file. + +package mcp + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "sync/atomic" + "testing" + + "github.com/google/jsonschema-go/jsonschema" +) + +func TestHasExtension(t *testing.T) { + tests := []struct { + name string + extensions map[string]any + lookup string + want bool + }{ + {"nil map", nil, ExtensionTasks, false}, + {"empty map", map[string]any{}, ExtensionTasks, false}, + {"absent", map[string]any{"io.example/other": map[string]any{}}, ExtensionTasks, false}, + {"present, empty settings", map[string]any{ExtensionTasks: map[string]any{}}, ExtensionTasks, true}, + {"present, with settings", map[string]any{ExtensionTasks: map[string]any{"k": "v"}}, ExtensionTasks, true}, + {"present, nil settings", map[string]any{ExtensionTasks: nil}, ExtensionTasks, true}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + client := &ClientCapabilities{Extensions: tc.extensions} + if got := client.HasExtension(tc.lookup); got != tc.want { + t.Errorf("ClientCapabilities.HasExtension(%q) = %v, want %v", tc.lookup, got, tc.want) + } + server := &ServerCapabilities{Extensions: tc.extensions} + if got := server.HasExtension(tc.lookup); got != tc.want { + t.Errorf("ServerCapabilities.HasExtension(%q) = %v, want %v", tc.lookup, got, tc.want) + } + }) + } + + t.Run("nil receiver", func(t *testing.T) { + var client *ClientCapabilities + if client.HasExtension(ExtensionTasks) { + t.Error("(*ClientCapabilities)(nil).HasExtension = true, want false") + } + var server *ServerCapabilities + if server.HasExtension(ExtensionTasks) { + t.Error("(*ServerCapabilities)(nil).HasExtension = true, want false") + } + }) + + t.Run("round trip with AddExtension", func(t *testing.T) { + client := new(ClientCapabilities) + client.AddExtension(ExtensionTasks, nil) + if !client.HasExtension(ExtensionTasks) { + t.Error("ClientCapabilities.HasExtension after AddExtension = false, want true") + } + server := new(ServerCapabilities) + server.AddExtension(ExtensionTasks, nil) + if !server.HasExtension(ExtensionTasks) { + t.Error("ServerCapabilities.HasExtension after AddExtension = false, want true") + } + }) +} + +// TestServerSeesClientTasksExtension checks that a client declaring the tasks +// extension is visible to a server request handler, across both capability +// transports: the per-request _meta of protocol 2026-07-28, and the +// initialize handshake of older versions. +// +// The declared=false cases also pin that the SDK never declares the extension +// on the user's behalf, since it does not implement task execution. +func TestServerSeesClientTasksExtension(t *testing.T) { + for _, version := range []string{protocolVersion20260728, protocolVersion20251125} { + t.Run(version, func(t *testing.T) { + for _, declare := range []bool{true, false} { + t.Run(fmt.Sprintf("declared=%t", declare), func(t *testing.T) { + ctx := context.Background() + + var got atomic.Bool + server := NewServer(testImpl, nil) + server.AddTool( + &Tool{Name: "probe", InputSchema: &jsonschema.Schema{Type: "object"}}, + func(ctx context.Context, req *CallToolRequest) (*CallToolResult, error) { + got.Store(req.ClientCapabilities().HasExtension(ExtensionTasks)) + return &CallToolResult{Content: []Content{&TextContent{Text: "ok"}}}, nil + }) + + clientOpts := new(ClientOptions) + if declare { + caps := new(ClientCapabilities) + caps.AddExtension(ExtensionTasks, nil) + clientOpts.Capabilities = caps + } + + ct, st := NewInMemoryTransports() + ss, err := server.Connect(ctx, st, nil) + if err != nil { + t.Fatalf("server Connect: %v", err) + } + defer ss.Close() + + cs, err := NewClient(testImpl, clientOpts).Connect(ctx, ct, &ClientSessionOptions{ProtocolVersion: version}) + if err != nil { + t.Fatalf("client Connect: %v", err) + } + defer cs.Close() + + if _, err := cs.CallTool(ctx, &CallToolParams{Name: "probe"}); err != nil { + t.Fatalf("CallTool: %v", err) + } + if got.Load() != declare { + t.Errorf("handler saw %s = %v, want %v", ExtensionTasks, got.Load(), declare) + } + }) + } + }) + } +} + +// TestClientSeesServerTasksExtension checks that a server declaring the tasks +// extension is visible to the client, both through server/discover and through +// the legacy initialize handshake. +func TestClientSeesServerTasksExtension(t *testing.T) { + for _, version := range []string{protocolVersion20260728, protocolVersion20251125} { + t.Run(version, func(t *testing.T) { + for _, declare := range []bool{true, false} { + t.Run(fmt.Sprintf("declared=%t", declare), func(t *testing.T) { + ctx := context.Background() + + serverOpts := new(ServerOptions) + if declare { + caps := new(ServerCapabilities) + caps.AddExtension(ExtensionTasks, nil) + serverOpts.Capabilities = caps + } + + ct, st := NewInMemoryTransports() + ss, err := NewServer(testImpl, serverOpts).Connect(ctx, st, nil) + if err != nil { + t.Fatalf("server Connect: %v", err) + } + defer ss.Close() + + cs, err := NewClient(testImpl, nil).Connect(ctx, ct, &ClientSessionOptions{ProtocolVersion: version}) + if err != nil { + t.Fatalf("client Connect: %v", err) + } + defer cs.Close() + + if got := cs.InitializeResult().Capabilities.HasExtension(ExtensionTasks); got != declare { + t.Errorf("client saw %s = %v, want %v", ExtensionTasks, got, declare) + } + }) + } + }) + } +} + +// TestUnmarshalTaskResult checks that a CreateTaskResult from the tasks +// extension is rejected rather than silently decoding into an empty result. +func TestUnmarshalTaskResult(t *testing.T) { + const taskResult = `{ + "resultType": "task", + "taskId": "786512e2", + "status": "working", + "createdAt": "2026-01-01T00:00:00Z", + "lastUpdatedAt": "2026-01-01T00:00:00Z", + "ttlMs": 60000, + "pollIntervalMs": 5000 + }` + + targets := []struct { + name string + newTarget func() any + }{ + {"CallToolResult", func() any { return new(CallToolResult) }}, + {"GetPromptResult", func() any { return new(GetPromptResult) }}, + {"ReadResourceResult", func() any { return new(ReadResourceResult) }}, + } + + for _, target := range targets { + t.Run(target.name, func(t *testing.T) { + t.Run("task is rejected", func(t *testing.T) { + err := json.Unmarshal([]byte(taskResult), target.newTarget()) + var terr *UnsupportedTaskResultError + if !errors.As(err, &terr) { + t.Fatalf("Unmarshal error = %v, want *UnsupportedTaskResultError", err) + } + if got, want := terr.TaskID, "786512e2"; got != want { + t.Errorf("TaskID = %q, want %q", got, want) + } + }) + + t.Run("complete still decodes", func(t *testing.T) { + if err := json.Unmarshal([]byte(`{"resultType":"complete"}`), target.newTarget()); err != nil { + t.Errorf("Unmarshal of a complete result failed: %v", err) + } + }) + }) + } +} + +// taskResultStub is a [Result] that marshals to a CreateTaskResult, standing in +// for a server that has decided to answer a request with a task handle. +type taskResultStub struct { + ResultBase + taskID string +} + +func (s *taskResultStub) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]any{ + "resultType": "task", + "taskId": s.taskID, + "status": "working", + "createdAt": "2026-01-01T00:00:00Z", + "lastUpdatedAt": "2026-01-01T00:00:00Z", + "ttlMs": 60000, + "pollIntervalMs": 5000, + }) +} + +// TestCallToolTaskResultEndToEnd checks that the decode guard survives the real +// client call path with its error identity intact, rather than surfacing as an +// empty but successful tool result. +func TestCallToolTaskResultEndToEnd(t *testing.T) { + ctx := context.Background() + + server := NewServer(testImpl, nil) + server.AddTool( + &Tool{Name: "probe", InputSchema: &jsonschema.Schema{Type: "object"}}, + func(ctx context.Context, req *CallToolRequest) (*CallToolResult, error) { + return nil, errors.New("unreachable: intercepted by middleware") + }) + server.AddReceivingMiddleware(func(next MethodHandler) MethodHandler { + return func(ctx context.Context, method string, req Request) (Result, error) { + if method == methodCallTool { + return &taskResultStub{taskID: "786512e2"}, nil + } + return next(ctx, method, req) + } + }) + + ct, st := NewInMemoryTransports() + ss, err := server.Connect(ctx, st, nil) + if err != nil { + t.Fatalf("server Connect: %v", err) + } + defer ss.Close() + + cs, err := NewClient(testImpl, nil).Connect(ctx, ct, nil) + if err != nil { + t.Fatalf("client Connect: %v", err) + } + defer cs.Close() + + res, err := cs.CallTool(ctx, &CallToolParams{Name: "probe"}) + if err == nil { + t.Fatalf("CallTool succeeded with %+v, want an error", res) + } + var terr *UnsupportedTaskResultError + if !errors.As(err, &terr) { + t.Fatalf("CallTool error = %v, want *UnsupportedTaskResultError", err) + } + if got, want := terr.TaskID, "786512e2"; got != want { + t.Errorf("TaskID = %q, want %q", got, want) + } +} From f76cafbd366614ffde372a709c52be42deb7c4e5 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Thu, 20 Aug 2026 11:45:45 +0200 Subject: [PATCH 2/2] docs: link the tasks extension spec instead of the SEP PR SEP-2663 has merged, and the tasks extension now has a published specification document. Point at both rather than at the SEP pull request and the ext-tasks repository root. --- docs/client.md | 4 ++-- docs/server.md | 4 ++-- internal/docs/client.src.md | 4 ++-- internal/docs/server.src.md | 4 ++-- mcp/tasks.go | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/client.md b/docs/client.md index fb5a3525..b22d99ce 100644 --- a/docs/client.md +++ b/docs/client.md @@ -350,9 +350,9 @@ if cs.InitializeResult().Capabilities.HasExtension("io.example/my-extension") { #### Tasks -[SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663) +[SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2663-tasks-extension.md) moved tasks out of the core protocol and into the -[tasks extension](https://github.com/modelcontextprotocol/ext-tasks), +[tasks extension](https://github.com/modelcontextprotocol/ext-tasks/blob/main/specification/draft/tasks.md), identified by the `mcp.ExtensionTasks` constant. A server that has negotiated it may answer a request with a durable task handle instead of the result that was asked for, which the client then polls to completion. diff --git a/docs/server.md b/docs/server.md index 7dfe9248..35e2777f 100644 --- a/docs/server.md +++ b/docs/server.md @@ -805,9 +805,9 @@ if req.ClientCapabilities().HasExtension("io.example/my-extension") { #### Tasks -[SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663) +[SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2663-tasks-extension.md) moved tasks out of the core protocol and into the -[tasks extension](https://github.com/modelcontextprotocol/ext-tasks), +[tasks extension](https://github.com/modelcontextprotocol/ext-tasks/blob/main/specification/draft/tasks.md), identified by the `mcp.ExtensionTasks` constant. A server that has negotiated it may answer a request with a durable task handle instead of the result that was asked for, which the client then polls to completion. diff --git a/internal/docs/client.src.md b/internal/docs/client.src.md index 234e3f47..20fe144a 100644 --- a/internal/docs/client.src.md +++ b/internal/docs/client.src.md @@ -199,9 +199,9 @@ if cs.InitializeResult().Capabilities.HasExtension("io.example/my-extension") { #### Tasks -[SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663) +[SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2663-tasks-extension.md) moved tasks out of the core protocol and into the -[tasks extension](https://github.com/modelcontextprotocol/ext-tasks), +[tasks extension](https://github.com/modelcontextprotocol/ext-tasks/blob/main/specification/draft/tasks.md), identified by the `mcp.ExtensionTasks` constant. A server that has negotiated it may answer a request with a durable task handle instead of the result that was asked for, which the client then polls to completion. diff --git a/internal/docs/server.src.md b/internal/docs/server.src.md index ee8ea216..3ad77763 100644 --- a/internal/docs/server.src.md +++ b/internal/docs/server.src.md @@ -423,9 +423,9 @@ if req.ClientCapabilities().HasExtension("io.example/my-extension") { #### Tasks -[SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663) +[SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2663-tasks-extension.md) moved tasks out of the core protocol and into the -[tasks extension](https://github.com/modelcontextprotocol/ext-tasks), +[tasks extension](https://github.com/modelcontextprotocol/ext-tasks/blob/main/specification/draft/tasks.md), identified by the `mcp.ExtensionTasks` constant. A server that has negotiated it may answer a request with a durable task handle instead of the result that was asked for, which the client then polls to completion. diff --git a/mcp/tasks.go b/mcp/tasks.go index 3459e9d9..5b0a8cf9 100644 --- a/mcp/tasks.go +++ b/mcp/tasks.go @@ -13,7 +13,7 @@ import "fmt" // extension by default: declaring it obliges a client to poll a task handle to // completion, and a server to serve the tasks/* methods. // -// See https://github.com/modelcontextprotocol/ext-tasks. +// See https://github.com/modelcontextprotocol/ext-tasks/blob/main/specification/draft/tasks.md. const ExtensionTasks = "io.modelcontextprotocol/tasks" // UnsupportedTaskResultError reports that a peer answered a request with a task